parent
70e3c242de
commit
34cab56437
4 changed files with 640 additions and 630 deletions
@ -1,53 +1,59 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
<groupId>org.fengfei</groupId> |
||||
<artifactId>lanproxy</artifactId> |
||||
<packaging>pom</packaging> |
||||
<version>0.1</version> |
||||
<name>lanproxy</name> |
||||
<url>http://maven.apache.org</url> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>io.netty</groupId> |
||||
<artifactId>netty-all</artifactId> |
||||
<version>4.0.36.Final</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.slf4j</groupId> |
||||
<artifactId>slf4j-api</artifactId> |
||||
<version>1.7.5</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.slf4j</groupId> |
||||
<artifactId>slf4j-log4j12</artifactId> |
||||
<version>1.7.5</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>junit</groupId> |
||||
<artifactId>junit</artifactId> |
||||
<version>4.12</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
<modules> |
||||
<module>proxy-common</module> |
||||
<module>proxy-protocol</module> |
||||
<module>proxy-server</module> |
||||
<module>proxy-client</module> |
||||
</modules> |
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-compiler-plugin</artifactId> |
||||
<version>3.1</version> |
||||
<configuration> |
||||
<source>1.8</source> |
||||
<target>1.8</target> |
||||
</configuration> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
</project> |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
<groupId>org.fengfei</groupId> |
||||
<artifactId>lanproxy</artifactId> |
||||
<packaging>pom</packaging> |
||||
<version>0.1</version> |
||||
<name>lanproxy</name> |
||||
<url>http://maven.apache.org</url> |
||||
<properties> |
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> |
||||
<maven.compiler.encoding>UTF-8</maven.compiler.encoding> |
||||
</properties> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>io.netty</groupId> |
||||
<artifactId>netty-all</artifactId> |
||||
<version>4.0.36.Final</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.slf4j</groupId> |
||||
<artifactId>slf4j-api</artifactId> |
||||
<version>1.7.5</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.slf4j</groupId> |
||||
<artifactId>slf4j-log4j12</artifactId> |
||||
<version>1.7.5</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>junit</groupId> |
||||
<artifactId>junit</artifactId> |
||||
<version>4.12</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
<modules> |
||||
<module>proxy-common</module> |
||||
<module>proxy-protocol</module> |
||||
<module>proxy-server</module> |
||||
<module>proxy-client</module> |
||||
</modules> |
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-compiler-plugin</artifactId> |
||||
<version>3.1</version> |
||||
<configuration> |
||||
<source>1.8</source> |
||||
<target>1.8</target> |
||||
<encoding>UTF-8</encoding> |
||||
</configuration> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
</project> |
||||
|
@ -1,399 +1,400 @@ |
||||
package org.fengfei.lanproxy.server.config; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.File; |
||||
import java.io.FileInputStream; |
||||
import java.io.FileOutputStream; |
||||
import java.io.InputStream; |
||||
import java.io.Serializable; |
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import org.fengfei.lanproxy.common.Config; |
||||
import org.fengfei.lanproxy.common.JsonUtil; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import com.google.gson.reflect.TypeToken; |
||||
|
||||
/** |
||||
* server config |
||||
* |
||||
* @author fengfei |
||||
* |
||||
*/ |
||||
public class ProxyConfig implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** 配置文件为config.json */ |
||||
public static final String CONFIG_FILE; |
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(ProxyConfig.class); |
||||
|
||||
static { |
||||
|
||||
// 代理配置信息存放在用户根目录下
|
||||
String dataPath = System.getProperty("user.home") + "/" + ".lanproxy/"; |
||||
File file = new File(dataPath); |
||||
if (!file.isDirectory()) { |
||||
file.mkdir(); |
||||
} |
||||
|
||||
CONFIG_FILE = dataPath + "/config.json"; |
||||
} |
||||
|
||||
/** 代理服务器绑定主机host */ |
||||
private String serverBind; |
||||
|
||||
/** 代理服务器与代理客户端通信端口 */ |
||||
private Integer serverPort; |
||||
|
||||
/** 配置服务绑定主机host */ |
||||
private String configServerBind; |
||||
|
||||
/** 配置服务端口 */ |
||||
private Integer configServerPort; |
||||
|
||||
/** 配置服务管理员用户名 */ |
||||
private String configAdminUsername; |
||||
|
||||
/** 配置服务管理员密码 */ |
||||
private String configAdminPassword; |
||||
|
||||
/** 代理客户端,支持多个客户端 */ |
||||
private List<Client> clients; |
||||
|
||||
/** 更新配置后保证在其他线程即时生效 */ |
||||
private static ProxyConfig instance = new ProxyConfig();; |
||||
|
||||
/** 代理服务器为各个代理客户端(key)开启对应的端口列表(value) */ |
||||
private volatile Map<String, List<Integer>> clientInetPortMapping = new HashMap<String, List<Integer>>(); |
||||
|
||||
/** 代理服务器上的每个对外端口(key)对应的代理客户端背后的真实服务器信息(value) */ |
||||
private volatile Map<Integer, String> inetPortLanInfoMapping = new HashMap<Integer, String>(); |
||||
|
||||
/** 配置变化监听器 */ |
||||
private List<ConfigChangedListener> configChangedListeners = new ArrayList<ConfigChangedListener>(); |
||||
|
||||
private ProxyConfig() { |
||||
|
||||
// 代理服务器主机和端口配置初始化
|
||||
this.serverPort = Config.getInstance().getIntValue("server.port"); |
||||
this.serverBind = Config.getInstance().getStringValue("server.bind", "0.0.0.0"); |
||||
|
||||
// 配置服务器主机和端口配置初始化
|
||||
this.configServerPort = Config.getInstance().getIntValue("config.server.port"); |
||||
this.configServerBind = Config.getInstance().getStringValue("config.server.bind", "0.0.0.0"); |
||||
|
||||
// 配置服务器管理员登录认证信息
|
||||
this.configAdminUsername = Config.getInstance().getStringValue("config.admin.username"); |
||||
this.configAdminPassword = Config.getInstance().getStringValue("config.admin.password"); |
||||
|
||||
logger.info( |
||||
"config init serverBind {}, serverPort {}, configServerBind {}, configServerPort {}, configAdminUsername {}, configAdminPassword {}", |
||||
serverBind, serverPort, configServerBind, configServerPort, configAdminUsername, configAdminPassword); |
||||
|
||||
update(null); |
||||
} |
||||
|
||||
public Integer getServerPort() { |
||||
return this.serverPort; |
||||
} |
||||
|
||||
public String getServerBind() { |
||||
return serverBind; |
||||
} |
||||
|
||||
public void setServerBind(String serverBind) { |
||||
this.serverBind = serverBind; |
||||
} |
||||
|
||||
public String getConfigServerBind() { |
||||
return configServerBind; |
||||
} |
||||
|
||||
public void setConfigServerBind(String configServerBind) { |
||||
this.configServerBind = configServerBind; |
||||
} |
||||
|
||||
public Integer getConfigServerPort() { |
||||
return configServerPort; |
||||
} |
||||
|
||||
public void setConfigServerPort(Integer configServerPort) { |
||||
this.configServerPort = configServerPort; |
||||
} |
||||
|
||||
public String getConfigAdminUsername() { |
||||
return configAdminUsername; |
||||
} |
||||
|
||||
public void setConfigAdminUsername(String configAdminUsername) { |
||||
this.configAdminUsername = configAdminUsername; |
||||
} |
||||
|
||||
public String getConfigAdminPassword() { |
||||
return configAdminPassword; |
||||
} |
||||
|
||||
public void setConfigAdminPassword(String configAdminPassword) { |
||||
this.configAdminPassword = configAdminPassword; |
||||
} |
||||
|
||||
public void setServerPort(Integer serverPort) { |
||||
this.serverPort = serverPort; |
||||
} |
||||
|
||||
public List<Client> getClients() { |
||||
return clients; |
||||
} |
||||
|
||||
/** |
||||
* 解析配置文件 |
||||
*/ |
||||
public void update(String proxyMappingConfigJson) { |
||||
|
||||
File file = new File(CONFIG_FILE); |
||||
try { |
||||
if (proxyMappingConfigJson == null && file.exists()) { |
||||
InputStream in = new FileInputStream(file); |
||||
byte[] buf = new byte[1024]; |
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
||||
int readIndex; |
||||
while ((readIndex = in.read(buf)) != -1) { |
||||
out.write(buf, 0, readIndex); |
||||
} |
||||
|
||||
in.close(); |
||||
proxyMappingConfigJson = new String(out.toByteArray()); |
||||
} |
||||
} catch (Exception e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
|
||||
List<Client> clients = JsonUtil.json2object(proxyMappingConfigJson, new TypeToken<List<Client>>() { |
||||
}); |
||||
if (clients == null) { |
||||
clients = new ArrayList<Client>(); |
||||
} |
||||
|
||||
Map<String, List<Integer>> clientInetPortMapping = new HashMap<String, List<Integer>>(); |
||||
Map<Integer, String> inetPortLanInfoMapping = new HashMap<Integer, String>(); |
||||
|
||||
// 构造端口映射关系
|
||||
for (Client client : clients) { |
||||
String clientKey = client.getClientKey(); |
||||
if (clientInetPortMapping.containsKey(clientKey)) { |
||||
throw new IllegalArgumentException("密钥同时作为客户端标识,不能重复: " + clientKey); |
||||
} |
||||
List<ClientProxyMapping> mappings = client.getProxyMappings(); |
||||
List<Integer> ports = new ArrayList<Integer>(); |
||||
clientInetPortMapping.put(clientKey, ports); |
||||
for (ClientProxyMapping mapping : mappings) { |
||||
Integer port = mapping.getInetPort(); |
||||
ports.add(port); |
||||
if (inetPortLanInfoMapping.containsKey(port)) { |
||||
throw new IllegalArgumentException("一个公网端口只能映射一个后端信息,不能重复: " + port); |
||||
} |
||||
|
||||
inetPortLanInfoMapping.put(port, mapping.getLan()); |
||||
} |
||||
} |
||||
|
||||
// 替换之前的配置关系
|
||||
this.clientInetPortMapping = clientInetPortMapping; |
||||
this.inetPortLanInfoMapping = inetPortLanInfoMapping; |
||||
this.clients = clients; |
||||
|
||||
try { |
||||
FileOutputStream out = new FileOutputStream(file); |
||||
out.write(proxyMappingConfigJson.getBytes()); |
||||
out.flush(); |
||||
out.close(); |
||||
} catch (Exception e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
|
||||
notifyconfigChangedListeners(); |
||||
} |
||||
|
||||
/** |
||||
* 配置更新通知 |
||||
*/ |
||||
private void notifyconfigChangedListeners() { |
||||
List<ConfigChangedListener> changedListeners = new ArrayList<ConfigChangedListener>(configChangedListeners); |
||||
for (ConfigChangedListener changedListener : changedListeners) { |
||||
changedListener.onChanged(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 添加配置变化监听器 |
||||
* |
||||
* @param configChangedListener |
||||
*/ |
||||
public void addConfigChangedListener(ConfigChangedListener configChangedListener) { |
||||
configChangedListeners.add(configChangedListener); |
||||
} |
||||
|
||||
/** |
||||
* 移除配置变化监听器 |
||||
* |
||||
* @param configChangedListener |
||||
*/ |
||||
public void removeConfigChangedListener(ConfigChangedListener configChangedListener) { |
||||
configChangedListeners.remove(configChangedListener); |
||||
} |
||||
|
||||
/** |
||||
* 获取代理客户端对应的代理服务器端口 |
||||
* |
||||
* @param clientKey |
||||
* @return |
||||
*/ |
||||
public List<Integer> getClientInetPorts(String clientKey) { |
||||
return clientInetPortMapping.get(clientKey); |
||||
} |
||||
|
||||
/** |
||||
* 获取所有的clientKey |
||||
* |
||||
* @return |
||||
*/ |
||||
public Set<String> getClientKeySet() { |
||||
return clientInetPortMapping.keySet(); |
||||
} |
||||
|
||||
/** |
||||
* 根据代理服务器端口获取后端服务器代理信息 |
||||
* |
||||
* @param port |
||||
* @return |
||||
*/ |
||||
public String getLanInfo(Integer port) { |
||||
return inetPortLanInfoMapping.get(port); |
||||
} |
||||
|
||||
/** |
||||
* 返回需要绑定在代理服务器的端口(用于用户请求) |
||||
* |
||||
* @return |
||||
*/ |
||||
public List<Integer> getUserPorts() { |
||||
List<Integer> ports = new ArrayList<Integer>(); |
||||
Iterator<Integer> ite = inetPortLanInfoMapping.keySet().iterator(); |
||||
while (ite.hasNext()) { |
||||
ports.add(ite.next()); |
||||
} |
||||
|
||||
return ports; |
||||
} |
||||
|
||||
public static ProxyConfig getInstance() { |
||||
return instance; |
||||
} |
||||
|
||||
/** |
||||
* 代理客户端 |
||||
* |
||||
* @author fengfei |
||||
* |
||||
*/ |
||||
public static class Client implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** 客户端备注名称 */ |
||||
private String name; |
||||
|
||||
/** 代理客户端唯一标识key */ |
||||
private String clientKey; |
||||
|
||||
/** 代理客户端与其后面的真实服务器映射关系 */ |
||||
private List<ClientProxyMapping> proxyMappings; |
||||
|
||||
public String getClientKey() { |
||||
return clientKey; |
||||
} |
||||
|
||||
public void setClientKey(String clientKey) { |
||||
this.clientKey = clientKey; |
||||
} |
||||
|
||||
public List<ClientProxyMapping> getProxyMappings() { |
||||
return proxyMappings; |
||||
} |
||||
|
||||
public void setProxyMappings(List<ClientProxyMapping> proxyMappings) { |
||||
this.proxyMappings = proxyMappings; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 代理客户端与其后面真实服务器映射关系 |
||||
* |
||||
* @author fengfei |
||||
* |
||||
*/ |
||||
public static class ClientProxyMapping { |
||||
|
||||
/** 代理服务器端口 */ |
||||
private Integer inetPort; |
||||
|
||||
/** 需要代理的网络信息(代理客户端能够访问),格式 192.168.1.99:80 (必须带端口) */ |
||||
private String lan; |
||||
|
||||
/** 备注名称 */ |
||||
private String name; |
||||
|
||||
public Integer getInetPort() { |
||||
return inetPort; |
||||
} |
||||
|
||||
public void setInetPort(Integer inetPort) { |
||||
this.inetPort = inetPort; |
||||
} |
||||
|
||||
public String getLan() { |
||||
return lan; |
||||
} |
||||
|
||||
public void setLan(String lan) { |
||||
this.lan = lan; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 配置更新回调 |
||||
* |
||||
* @author fengfei |
||||
* |
||||
*/ |
||||
public static interface ConfigChangedListener { |
||||
|
||||
void onChanged(); |
||||
} |
||||
} |
||||
package org.fengfei.lanproxy.server.config; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.File; |
||||
import java.io.FileInputStream; |
||||
import java.io.FileOutputStream; |
||||
import java.io.InputStream; |
||||
import java.io.Serializable; |
||||
import java.nio.charset.Charset; |
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import org.fengfei.lanproxy.common.Config; |
||||
import org.fengfei.lanproxy.common.JsonUtil; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import com.google.gson.reflect.TypeToken; |
||||
|
||||
/** |
||||
* server config |
||||
* |
||||
* @author fengfei |
||||
* |
||||
*/ |
||||
public class ProxyConfig implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** 配置文件为config.json */ |
||||
public static final String CONFIG_FILE; |
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(ProxyConfig.class); |
||||
|
||||
static { |
||||
|
||||
// 代理配置信息存放在用户根目录下
|
||||
String dataPath = System.getProperty("user.home") + "/" + ".lanproxy/"; |
||||
File file = new File(dataPath); |
||||
if (!file.isDirectory()) { |
||||
file.mkdir(); |
||||
} |
||||
|
||||
CONFIG_FILE = dataPath + "/config.json"; |
||||
} |
||||
|
||||
/** 代理服务器绑定主机host */ |
||||
private String serverBind; |
||||
|
||||
/** 代理服务器与代理客户端通信端口 */ |
||||
private Integer serverPort; |
||||
|
||||
/** 配置服务绑定主机host */ |
||||
private String configServerBind; |
||||
|
||||
/** 配置服务端口 */ |
||||
private Integer configServerPort; |
||||
|
||||
/** 配置服务管理员用户名 */ |
||||
private String configAdminUsername; |
||||
|
||||
/** 配置服务管理员密码 */ |
||||
private String configAdminPassword; |
||||
|
||||
/** 代理客户端,支持多个客户端 */ |
||||
private List<Client> clients; |
||||
|
||||
/** 更新配置后保证在其他线程即时生效 */ |
||||
private static ProxyConfig instance = new ProxyConfig();; |
||||
|
||||
/** 代理服务器为各个代理客户端(key)开启对应的端口列表(value) */ |
||||
private volatile Map<String, List<Integer>> clientInetPortMapping = new HashMap<String, List<Integer>>(); |
||||
|
||||
/** 代理服务器上的每个对外端口(key)对应的代理客户端背后的真实服务器信息(value) */ |
||||
private volatile Map<Integer, String> inetPortLanInfoMapping = new HashMap<Integer, String>(); |
||||
|
||||
/** 配置变化监听器 */ |
||||
private List<ConfigChangedListener> configChangedListeners = new ArrayList<ConfigChangedListener>(); |
||||
|
||||
private ProxyConfig() { |
||||
|
||||
// 代理服务器主机和端口配置初始化
|
||||
this.serverPort = Config.getInstance().getIntValue("server.port"); |
||||
this.serverBind = Config.getInstance().getStringValue("server.bind", "0.0.0.0"); |
||||
|
||||
// 配置服务器主机和端口配置初始化
|
||||
this.configServerPort = Config.getInstance().getIntValue("config.server.port"); |
||||
this.configServerBind = Config.getInstance().getStringValue("config.server.bind", "0.0.0.0"); |
||||
|
||||
// 配置服务器管理员登录认证信息
|
||||
this.configAdminUsername = Config.getInstance().getStringValue("config.admin.username"); |
||||
this.configAdminPassword = Config.getInstance().getStringValue("config.admin.password"); |
||||
|
||||
logger.info( |
||||
"config init serverBind {}, serverPort {}, configServerBind {}, configServerPort {}, configAdminUsername {}, configAdminPassword {}", |
||||
serverBind, serverPort, configServerBind, configServerPort, configAdminUsername, configAdminPassword); |
||||
|
||||
update(null); |
||||
} |
||||
|
||||
public Integer getServerPort() { |
||||
return this.serverPort; |
||||
} |
||||
|
||||
public String getServerBind() { |
||||
return serverBind; |
||||
} |
||||
|
||||
public void setServerBind(String serverBind) { |
||||
this.serverBind = serverBind; |
||||
} |
||||
|
||||
public String getConfigServerBind() { |
||||
return configServerBind; |
||||
} |
||||
|
||||
public void setConfigServerBind(String configServerBind) { |
||||
this.configServerBind = configServerBind; |
||||
} |
||||
|
||||
public Integer getConfigServerPort() { |
||||
return configServerPort; |
||||
} |
||||
|
||||
public void setConfigServerPort(Integer configServerPort) { |
||||
this.configServerPort = configServerPort; |
||||
} |
||||
|
||||
public String getConfigAdminUsername() { |
||||
return configAdminUsername; |
||||
} |
||||
|
||||
public void setConfigAdminUsername(String configAdminUsername) { |
||||
this.configAdminUsername = configAdminUsername; |
||||
} |
||||
|
||||
public String getConfigAdminPassword() { |
||||
return configAdminPassword; |
||||
} |
||||
|
||||
public void setConfigAdminPassword(String configAdminPassword) { |
||||
this.configAdminPassword = configAdminPassword; |
||||
} |
||||
|
||||
public void setServerPort(Integer serverPort) { |
||||
this.serverPort = serverPort; |
||||
} |
||||
|
||||
public List<Client> getClients() { |
||||
return clients; |
||||
} |
||||
|
||||
/** |
||||
* 解析配置文件 |
||||
*/ |
||||
public void update(String proxyMappingConfigJson) { |
||||
|
||||
File file = new File(CONFIG_FILE); |
||||
try { |
||||
if (proxyMappingConfigJson == null && file.exists()) { |
||||
InputStream in = new FileInputStream(file); |
||||
byte[] buf = new byte[1024]; |
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
||||
int readIndex; |
||||
while ((readIndex = in.read(buf)) != -1) { |
||||
out.write(buf, 0, readIndex); |
||||
} |
||||
|
||||
in.close(); |
||||
proxyMappingConfigJson = new String(out.toByteArray(), Charset.forName("UTF-8")); |
||||
} |
||||
} catch (Exception e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
|
||||
List<Client> clients = JsonUtil.json2object(proxyMappingConfigJson, new TypeToken<List<Client>>() { |
||||
}); |
||||
if (clients == null) { |
||||
clients = new ArrayList<Client>(); |
||||
} |
||||
|
||||
Map<String, List<Integer>> clientInetPortMapping = new HashMap<String, List<Integer>>(); |
||||
Map<Integer, String> inetPortLanInfoMapping = new HashMap<Integer, String>(); |
||||
|
||||
// 构造端口映射关系
|
||||
for (Client client : clients) { |
||||
String clientKey = client.getClientKey(); |
||||
if (clientInetPortMapping.containsKey(clientKey)) { |
||||
throw new IllegalArgumentException("密钥同时作为客户端标识,不能重复: " + clientKey); |
||||
} |
||||
List<ClientProxyMapping> mappings = client.getProxyMappings(); |
||||
List<Integer> ports = new ArrayList<Integer>(); |
||||
clientInetPortMapping.put(clientKey, ports); |
||||
for (ClientProxyMapping mapping : mappings) { |
||||
Integer port = mapping.getInetPort(); |
||||
ports.add(port); |
||||
if (inetPortLanInfoMapping.containsKey(port)) { |
||||
throw new IllegalArgumentException("一个公网端口只能映射一个后端信息,不能重复: " + port); |
||||
} |
||||
|
||||
inetPortLanInfoMapping.put(port, mapping.getLan()); |
||||
} |
||||
} |
||||
|
||||
// 替换之前的配置关系
|
||||
this.clientInetPortMapping = clientInetPortMapping; |
||||
this.inetPortLanInfoMapping = inetPortLanInfoMapping; |
||||
this.clients = clients; |
||||
|
||||
try { |
||||
FileOutputStream out = new FileOutputStream(file); |
||||
out.write(proxyMappingConfigJson.getBytes(Charset.forName("UTF-8"))); |
||||
out.flush(); |
||||
out.close(); |
||||
} catch (Exception e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
|
||||
notifyconfigChangedListeners(); |
||||
} |
||||
|
||||
/** |
||||
* 配置更新通知 |
||||
*/ |
||||
private void notifyconfigChangedListeners() { |
||||
List<ConfigChangedListener> changedListeners = new ArrayList<ConfigChangedListener>(configChangedListeners); |
||||
for (ConfigChangedListener changedListener : changedListeners) { |
||||
changedListener.onChanged(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 添加配置变化监听器 |
||||
* |
||||
* @param configChangedListener |
||||
*/ |
||||
public void addConfigChangedListener(ConfigChangedListener configChangedListener) { |
||||
configChangedListeners.add(configChangedListener); |
||||
} |
||||
|
||||
/** |
||||
* 移除配置变化监听器 |
||||
* |
||||
* @param configChangedListener |
||||
*/ |
||||
public void removeConfigChangedListener(ConfigChangedListener configChangedListener) { |
||||
configChangedListeners.remove(configChangedListener); |
||||
} |
||||
|
||||
/** |
||||
* 获取代理客户端对应的代理服务器端口 |
||||
* |
||||
* @param clientKey |
||||
* @return |
||||
*/ |
||||
public List<Integer> getClientInetPorts(String clientKey) { |
||||
return clientInetPortMapping.get(clientKey); |
||||
} |
||||
|
||||
/** |
||||
* 获取所有的clientKey |
||||
* |
||||
* @return |
||||
*/ |
||||
public Set<String> getClientKeySet() { |
||||
return clientInetPortMapping.keySet(); |
||||
} |
||||
|
||||
/** |
||||
* 根据代理服务器端口获取后端服务器代理信息 |
||||
* |
||||
* @param port |
||||
* @return |
||||
*/ |
||||
public String getLanInfo(Integer port) { |
||||
return inetPortLanInfoMapping.get(port); |
||||
} |
||||
|
||||
/** |
||||
* 返回需要绑定在代理服务器的端口(用于用户请求) |
||||
* |
||||
* @return |
||||
*/ |
||||
public List<Integer> getUserPorts() { |
||||
List<Integer> ports = new ArrayList<Integer>(); |
||||
Iterator<Integer> ite = inetPortLanInfoMapping.keySet().iterator(); |
||||
while (ite.hasNext()) { |
||||
ports.add(ite.next()); |
||||
} |
||||
|
||||
return ports; |
||||
} |
||||
|
||||
public static ProxyConfig getInstance() { |
||||
return instance; |
||||
} |
||||
|
||||
/** |
||||
* 代理客户端 |
||||
* |
||||
* @author fengfei |
||||
* |
||||
*/ |
||||
public static class Client implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
/** 客户端备注名称 */ |
||||
private String name; |
||||
|
||||
/** 代理客户端唯一标识key */ |
||||
private String clientKey; |
||||
|
||||
/** 代理客户端与其后面的真实服务器映射关系 */ |
||||
private List<ClientProxyMapping> proxyMappings; |
||||
|
||||
public String getClientKey() { |
||||
return clientKey; |
||||
} |
||||
|
||||
public void setClientKey(String clientKey) { |
||||
this.clientKey = clientKey; |
||||
} |
||||
|
||||
public List<ClientProxyMapping> getProxyMappings() { |
||||
return proxyMappings; |
||||
} |
||||
|
||||
public void setProxyMappings(List<ClientProxyMapping> proxyMappings) { |
||||
this.proxyMappings = proxyMappings; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 代理客户端与其后面真实服务器映射关系 |
||||
* |
||||
* @author fengfei |
||||
* |
||||
*/ |
||||
public static class ClientProxyMapping { |
||||
|
||||
/** 代理服务器端口 */ |
||||
private Integer inetPort; |
||||
|
||||
/** 需要代理的网络信息(代理客户端能够访问),格式 192.168.1.99:80 (必须带端口) */ |
||||
private String lan; |
||||
|
||||
/** 备注名称 */ |
||||
private String name; |
||||
|
||||
public Integer getInetPort() { |
||||
return inetPort; |
||||
} |
||||
|
||||
public void setInetPort(Integer inetPort) { |
||||
this.inetPort = inetPort; |
||||
} |
||||
|
||||
public String getLan() { |
||||
return lan; |
||||
} |
||||
|
||||
public void setLan(String lan) { |
||||
this.lan = lan; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 配置更新回调 |
||||
* |
||||
* @author fengfei |
||||
* |
||||
*/ |
||||
public static interface ConfigChangedListener { |
||||
|
||||
void onChanged(); |
||||
} |
||||
} |
||||
|
@ -1,164 +1,167 @@ |
||||
package org.fengfei.lanproxy.server.config.web.routes; |
||||
|
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.UUID; |
||||
|
||||
import org.fengfei.lanproxy.common.JsonUtil; |
||||
import org.fengfei.lanproxy.server.config.ProxyConfig; |
||||
import org.fengfei.lanproxy.server.config.ProxyConfig.Client; |
||||
import org.fengfei.lanproxy.server.config.web.ApiRoute; |
||||
import org.fengfei.lanproxy.server.config.web.RequestHandler; |
||||
import org.fengfei.lanproxy.server.config.web.RequestMiddleware; |
||||
import org.fengfei.lanproxy.server.config.web.ResponseInfo; |
||||
import org.fengfei.lanproxy.server.config.web.exception.ContextException; |
||||
import org.fengfei.lanproxy.server.metrics.MetricsCollector; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import com.google.gson.reflect.TypeToken; |
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest; |
||||
import io.netty.handler.codec.http.HttpHeaders; |
||||
|
||||
/** |
||||
* 接口实现 |
||||
* |
||||
* @author fengfei |
||||
* |
||||
*/ |
||||
public class RouteConfig { |
||||
|
||||
protected static final String AUTH_COOKIE_KEY = "token"; |
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(RouteConfig.class); |
||||
|
||||
/** 管理员不能同时在多个地方登录 */ |
||||
private static String token; |
||||
|
||||
public static void init() { |
||||
|
||||
ApiRoute.addMiddleware(new RequestMiddleware() { |
||||
|
||||
@Override |
||||
public void preRequest(FullHttpRequest request) { |
||||
String cookieHeader = request.headers().get(HttpHeaders.Names.COOKIE); |
||||
boolean authenticated = false; |
||||
if (cookieHeader != null) { |
||||
String[] cookies = cookieHeader.split(";"); |
||||
for (String cookie : cookies) { |
||||
String[] cookieArr = cookie.split("="); |
||||
if (AUTH_COOKIE_KEY.equals(cookieArr[0].trim())) { |
||||
if (cookieArr.length == 2 && cookieArr[1].equals(token)) { |
||||
authenticated = true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
String auth = request.headers().get(HttpHeaders.Names.AUTHORIZATION); |
||||
if (!authenticated && auth != null) { |
||||
String[] authArr = auth.split(" "); |
||||
if (authArr.length == 2 && authArr[0].equals(ProxyConfig.getInstance().getConfigAdminUsername()) && authArr[1].equals(ProxyConfig.getInstance().getConfigAdminPassword())) { |
||||
authenticated = true; |
||||
} |
||||
} |
||||
|
||||
if (!request.getUri().equals("/login") && !authenticated) { |
||||
throw new ContextException(ResponseInfo.CODE_UNAUTHORIZED); |
||||
} |
||||
|
||||
logger.info("handle request for api {}", request.getUri()); |
||||
} |
||||
}); |
||||
|
||||
// 获取配置详细信息
|
||||
ApiRoute.addRoute("/config/detail", new RequestHandler() { |
||||
|
||||
@Override |
||||
public ResponseInfo request(FullHttpRequest request) { |
||||
|
||||
return ResponseInfo.build(ProxyConfig.getInstance().getClients()); |
||||
} |
||||
}); |
||||
|
||||
// 更新配置
|
||||
ApiRoute.addRoute("/config/update", new RequestHandler() { |
||||
|
||||
@Override |
||||
public ResponseInfo request(FullHttpRequest request) { |
||||
byte[] buf = new byte[request.content().readableBytes()]; |
||||
request.content().readBytes(buf); |
||||
String config = new String(buf); |
||||
List<Client> clients = JsonUtil.json2object(config, new TypeToken<List<Client>>() { |
||||
}); |
||||
if (clients == null) { |
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error json config"); |
||||
} |
||||
|
||||
try { |
||||
ProxyConfig.getInstance().update(config); |
||||
} catch (Exception ex) { |
||||
logger.error("config update error", ex); |
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, ex.getMessage()); |
||||
} |
||||
|
||||
return ResponseInfo.build(ResponseInfo.CODE_OK, "success"); |
||||
} |
||||
}); |
||||
|
||||
ApiRoute.addRoute("/login", new RequestHandler() { |
||||
|
||||
@Override |
||||
public ResponseInfo request(FullHttpRequest request) { |
||||
byte[] buf = new byte[request.content().readableBytes()]; |
||||
request.content().readBytes(buf); |
||||
String config = new String(buf); |
||||
Map<String, String> loginParams = JsonUtil.json2object(config, new TypeToken<Map<String, String>>() { |
||||
}); |
||||
if (loginParams == null) { |
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error login info"); |
||||
} |
||||
|
||||
String username = loginParams.get("username"); |
||||
String password = loginParams.get("password"); |
||||
if (username == null || password == null) { |
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error username or password"); |
||||
} |
||||
|
||||
if (username.equals(ProxyConfig.getInstance().getConfigAdminUsername()) && password.equals(ProxyConfig.getInstance().getConfigAdminPassword())) { |
||||
token = UUID.randomUUID().toString().replace("-", ""); |
||||
return ResponseInfo.build(token); |
||||
} |
||||
|
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error username or password"); |
||||
} |
||||
}); |
||||
|
||||
ApiRoute.addRoute("/logout", new RequestHandler() { |
||||
|
||||
@Override |
||||
public ResponseInfo request(FullHttpRequest request) { |
||||
token = null; |
||||
return ResponseInfo.build(ResponseInfo.CODE_OK, "success"); |
||||
} |
||||
}); |
||||
|
||||
ApiRoute.addRoute("/metrics/get", new RequestHandler() { |
||||
|
||||
@Override |
||||
public ResponseInfo request(FullHttpRequest request) { |
||||
return ResponseInfo.build(MetricsCollector.getAllMetrics()); |
||||
} |
||||
}); |
||||
|
||||
ApiRoute.addRoute("/metrics/getandreset", new RequestHandler() { |
||||
|
||||
@Override |
||||
public ResponseInfo request(FullHttpRequest request) { |
||||
return ResponseInfo.build(MetricsCollector.getAndResetAllMetrics()); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
} |
||||
package org.fengfei.lanproxy.server.config.web.routes; |
||||
|
||||
import java.nio.charset.Charset; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.UUID; |
||||
|
||||
import org.fengfei.lanproxy.common.JsonUtil; |
||||
import org.fengfei.lanproxy.server.config.ProxyConfig; |
||||
import org.fengfei.lanproxy.server.config.ProxyConfig.Client; |
||||
import org.fengfei.lanproxy.server.config.web.ApiRoute; |
||||
import org.fengfei.lanproxy.server.config.web.RequestHandler; |
||||
import org.fengfei.lanproxy.server.config.web.RequestMiddleware; |
||||
import org.fengfei.lanproxy.server.config.web.ResponseInfo; |
||||
import org.fengfei.lanproxy.server.config.web.exception.ContextException; |
||||
import org.fengfei.lanproxy.server.metrics.MetricsCollector; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import com.google.gson.reflect.TypeToken; |
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest; |
||||
import io.netty.handler.codec.http.HttpHeaders; |
||||
|
||||
/** |
||||
* 接口实现 |
||||
* |
||||
* @author fengfei |
||||
* |
||||
*/ |
||||
public class RouteConfig { |
||||
|
||||
protected static final String AUTH_COOKIE_KEY = "token"; |
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(RouteConfig.class); |
||||
|
||||
/** 管理员不能同时在多个地方登录 */ |
||||
private static String token; |
||||
|
||||
public static void init() { |
||||
|
||||
ApiRoute.addMiddleware(new RequestMiddleware() { |
||||
|
||||
@Override |
||||
public void preRequest(FullHttpRequest request) { |
||||
String cookieHeader = request.headers().get(HttpHeaders.Names.COOKIE); |
||||
boolean authenticated = false; |
||||
if (cookieHeader != null) { |
||||
String[] cookies = cookieHeader.split(";"); |
||||
for (String cookie : cookies) { |
||||
String[] cookieArr = cookie.split("="); |
||||
if (AUTH_COOKIE_KEY.equals(cookieArr[0].trim())) { |
||||
if (cookieArr.length == 2 && cookieArr[1].equals(token)) { |
||||
authenticated = true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
String auth = request.headers().get(HttpHeaders.Names.AUTHORIZATION); |
||||
if (!authenticated && auth != null) { |
||||
String[] authArr = auth.split(" "); |
||||
if (authArr.length == 2 && authArr[0].equals(ProxyConfig.getInstance().getConfigAdminUsername()) |
||||
&& authArr[1].equals(ProxyConfig.getInstance().getConfigAdminPassword())) { |
||||
authenticated = true; |
||||
} |
||||
} |
||||
|
||||
if (!request.getUri().equals("/login") && !authenticated) { |
||||
throw new ContextException(ResponseInfo.CODE_UNAUTHORIZED); |
||||
} |
||||
|
||||
logger.info("handle request for api {}", request.getUri()); |
||||
} |
||||
}); |
||||
|
||||
// 获取配置详细信息
|
||||
ApiRoute.addRoute("/config/detail", new RequestHandler() { |
||||
|
||||
@Override |
||||
public ResponseInfo request(FullHttpRequest request) { |
||||
|
||||
return ResponseInfo.build(ProxyConfig.getInstance().getClients()); |
||||
} |
||||
}); |
||||
|
||||
// 更新配置
|
||||
ApiRoute.addRoute("/config/update", new RequestHandler() { |
||||
|
||||
@Override |
||||
public ResponseInfo request(FullHttpRequest request) { |
||||
byte[] buf = new byte[request.content().readableBytes()]; |
||||
request.content().readBytes(buf); |
||||
String config = new String(buf, Charset.forName("UTF-8")); |
||||
List<Client> clients = JsonUtil.json2object(config, new TypeToken<List<Client>>() { |
||||
}); |
||||
if (clients == null) { |
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error json config"); |
||||
} |
||||
|
||||
try { |
||||
ProxyConfig.getInstance().update(config); |
||||
} catch (Exception ex) { |
||||
logger.error("config update error", ex); |
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, ex.getMessage()); |
||||
} |
||||
|
||||
return ResponseInfo.build(ResponseInfo.CODE_OK, "success"); |
||||
} |
||||
}); |
||||
|
||||
ApiRoute.addRoute("/login", new RequestHandler() { |
||||
|
||||
@Override |
||||
public ResponseInfo request(FullHttpRequest request) { |
||||
byte[] buf = new byte[request.content().readableBytes()]; |
||||
request.content().readBytes(buf); |
||||
String config = new String(buf); |
||||
Map<String, String> loginParams = JsonUtil.json2object(config, new TypeToken<Map<String, String>>() { |
||||
}); |
||||
if (loginParams == null) { |
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error login info"); |
||||
} |
||||
|
||||
String username = loginParams.get("username"); |
||||
String password = loginParams.get("password"); |
||||
if (username == null || password == null) { |
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error username or password"); |
||||
} |
||||
|
||||
if (username.equals(ProxyConfig.getInstance().getConfigAdminUsername()) |
||||
&& password.equals(ProxyConfig.getInstance().getConfigAdminPassword())) { |
||||
token = UUID.randomUUID().toString().replace("-", ""); |
||||
return ResponseInfo.build(token); |
||||
} |
||||
|
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error username or password"); |
||||
} |
||||
}); |
||||
|
||||
ApiRoute.addRoute("/logout", new RequestHandler() { |
||||
|
||||
@Override |
||||
public ResponseInfo request(FullHttpRequest request) { |
||||
token = null; |
||||
return ResponseInfo.build(ResponseInfo.CODE_OK, "success"); |
||||
} |
||||
}); |
||||
|
||||
ApiRoute.addRoute("/metrics/get", new RequestHandler() { |
||||
|
||||
@Override |
||||
public ResponseInfo request(FullHttpRequest request) { |
||||
return ResponseInfo.build(MetricsCollector.getAllMetrics()); |
||||
} |
||||
}); |
||||
|
||||
ApiRoute.addRoute("/metrics/getandreset", new RequestHandler() { |
||||
|
||||
@Override |
||||
public ResponseInfo request(FullHttpRequest request) { |
||||
return ResponseInfo.build(MetricsCollector.getAndResetAllMetrics()); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
} |
||||
|
@ -1,15 +1,15 @@ |
||||
server.bind=0.0.0.0 |
||||
server.port=4900 |
||||
|
||||
server.ssl.enable=true |
||||
server.ssl.bind=0.0.0.0 |
||||
server.ssl.port=4993 |
||||
server.ssl.jksPath=test.jks |
||||
server.ssl.keyStorePassword=123456 |
||||
server.ssl.keyManagerPassword=123456 |
||||
server.ssl.needsClientAuth=false |
||||
|
||||
config.server.bind=0.0.0.0 |
||||
config.server.port=8082 |
||||
config.admin.username=admin |
||||
server.bind=0.0.0.0 |
||||
server.port=4900 |
||||
|
||||
server.ssl.enable=true |
||||
server.ssl.bind=0.0.0.0 |
||||
server.ssl.port=4993 |
||||
server.ssl.jksPath=test.jks |
||||
server.ssl.keyStorePassword=123456 |
||||
server.ssl.keyManagerPassword=123456 |
||||
server.ssl.needsClientAuth=false |
||||
|
||||
config.server.bind=0.0.0.0 |
||||
config.server.port=8090 |
||||
config.admin.username=admin |
||||
config.admin.password=admin |
Loading…
Reference in new issue