增加域名映射支持
This commit is contained in:
+1
@@ -44,6 +44,7 @@ public class ProxyConfig {
|
||||
private String jksPath;
|
||||
private Integer bossThreadCount;
|
||||
private Integer workThreadCount;
|
||||
private String domainName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -55,6 +55,9 @@ public enum ExceptionConstant {
|
||||
// 端口映射管理(14000)
|
||||
PORT_MAPPING_NOT_EXIST(14000, "端口映射记录不存在"),
|
||||
PORT_CANNOT_REPEAT_MAPPING(14001, "服务端口[{}]不能重复映射"),
|
||||
AN_UNSUPPORTED_PROTOCOL(14002, "不支持的协议[{}]!"),
|
||||
PORT_MAPPING_SUBDONAME_CONNOT_REPEAT(14003, "子域名不能重复使用!"),
|
||||
|
||||
// 调度管理(15000)
|
||||
JOB_INFO_NOT_EXIST(15000, "调度管理记录不存在"),
|
||||
SYSTEM_ERROR(500, "系统异常"),
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package org.dromara.neutrinoproxy.server.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum NetworkProtocolEnum {
|
||||
TCP("TCP"),
|
||||
UDP("UDP"),
|
||||
HTTP("HTTP"),
|
||||
;
|
||||
private String desc;
|
||||
private static final Map<String, NetworkProtocolEnum> map = Stream.of(NetworkProtocolEnum.values()).collect(Collectors.toMap(NetworkProtocolEnum::getDesc, Function.identity()));
|
||||
|
||||
public static NetworkProtocolEnum of(String desc) {
|
||||
return map.get(desc);
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Get;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
@Slf4j
|
||||
@Mapping("/domain-name")
|
||||
@Controller
|
||||
public class DomainNameController {
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
|
||||
@Get
|
||||
@Mapping("/bind-info")
|
||||
public String bindInfo () {
|
||||
return proxyConfig.getServer().getDomainName();
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -2,6 +2,8 @@ package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.*;
|
||||
@@ -37,10 +39,17 @@ public class PortMappingController {
|
||||
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
|
||||
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
|
||||
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
|
||||
ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal");
|
||||
if (StringUtils.isBlank(req.getClientIp())) {
|
||||
// 没传客户端ip,默认为127.0.0.1
|
||||
req.setClientIp("127.0.0.1");
|
||||
}
|
||||
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal());
|
||||
ParamCheckUtil.checkNotNull(networkProtocolEnum, ExceptionConstant.AN_UNSUPPORTED_PROTOCOL, req.getProtocal());
|
||||
if (networkProtocolEnum != NetworkProtocolEnum.HTTP) {
|
||||
// 目前仅HTTP支持绑定域名
|
||||
req.setSubdomain("");
|
||||
}
|
||||
|
||||
return portMappingService.create(req);
|
||||
}
|
||||
@@ -49,6 +58,20 @@ public class PortMappingController {
|
||||
@Mapping("/update")
|
||||
public PortMappingUpdateRes update(PortMappingUpdateReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
|
||||
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
|
||||
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
|
||||
ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal");
|
||||
if (StringUtils.isBlank(req.getClientIp())) {
|
||||
// 没传客户端ip,默认为127.0.0.1
|
||||
req.setClientIp("127.0.0.1");
|
||||
}
|
||||
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal());
|
||||
ParamCheckUtil.checkNotNull(networkProtocolEnum, ExceptionConstant.AN_UNSUPPORTED_PROTOCOL, req.getProtocal());
|
||||
if (networkProtocolEnum != NetworkProtocolEnum.HTTP) {
|
||||
// 目前仅HTTP支持绑定域名
|
||||
req.setSubdomain("");
|
||||
}
|
||||
|
||||
return portMappingService.update(req);
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes;
|
||||
import org.dromara.neutrinoproxy.server.service.ProtocalService;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Get;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
@Mapping("/protocal")
|
||||
@Controller
|
||||
public class ProtocalController {
|
||||
|
||||
@Inject
|
||||
private ProtocalService protocalService;
|
||||
|
||||
@Get
|
||||
@Mapping("/page")
|
||||
public PageInfo<ProtocalListRes> page(PageQuery pageQuery) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
|
||||
List<ProtocalListRes> list = protocalService.list();
|
||||
return PageInfo.of(protocalService.list(), (long)list.size(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
|
||||
@Get
|
||||
@Mapping("/list")
|
||||
public List<ProtocalListRes> list() {
|
||||
return protocalService.list();
|
||||
}
|
||||
}
|
||||
+8
@@ -34,6 +34,14 @@ public class PortMappingCreateReq {
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* 协议
|
||||
*/
|
||||
private String protocal;
|
||||
/**
|
||||
* 子域名
|
||||
*/
|
||||
private String subdomain;
|
||||
/**
|
||||
* 服务端端口
|
||||
*/
|
||||
|
||||
+4
@@ -36,6 +36,10 @@ public class PortMappingListReq {
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 协议
|
||||
*/
|
||||
private String protocal;
|
||||
/**
|
||||
* licenseId
|
||||
*/
|
||||
|
||||
+8
@@ -38,6 +38,14 @@ public class PortMappingUpdateReq {
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* 协议
|
||||
*/
|
||||
private String protocal;
|
||||
/**
|
||||
* 子域名
|
||||
*/
|
||||
private String subdomain;
|
||||
/**
|
||||
* 服务端端口
|
||||
*/
|
||||
|
||||
+12
@@ -39,6 +39,18 @@ public class PortMappingListRes {
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* 协议
|
||||
*/
|
||||
private String protocal;
|
||||
/**
|
||||
* 子域名
|
||||
*/
|
||||
private String subdomain;
|
||||
/**
|
||||
* 域名
|
||||
*/
|
||||
private String domain;
|
||||
/**
|
||||
* license名称
|
||||
*/
|
||||
|
||||
+2
@@ -22,6 +22,7 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -30,6 +31,7 @@ import java.util.Date;
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/7
|
||||
*/
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class PortPoolListRes {
|
||||
private Integer id;
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 协议列表响应
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class ProtocalListRes {
|
||||
/**
|
||||
* 协议名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 启用状态
|
||||
*/
|
||||
private Boolean enable;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
}
|
||||
+12
@@ -42,6 +42,18 @@ public interface PortMappingMapper extends BaseMapper<PortMappingDO> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验子域名是否重复
|
||||
* @param subdomain
|
||||
* @return
|
||||
*/
|
||||
default Boolean checkRepeatBySubdomain(String subdomain, Set<Integer> excludeIds) {
|
||||
return this.selectCount(new LambdaQueryWrapper<PortMappingDO>()
|
||||
.eq(PortMappingDO::getSubdomain, subdomain)
|
||||
.notIn(!CollectionUtil.isEmpty(excludeIds), PortMappingDO::getId, excludeIds)
|
||||
).intValue() > 0;
|
||||
}
|
||||
|
||||
default List<PortMappingDO> findEnableListByLicenseId(Integer licenseId) {
|
||||
return this.selectList(new LambdaQueryWrapper<PortMappingDO>()
|
||||
.eq(PortMappingDO::getLicenseId, licenseId)
|
||||
|
||||
+8
@@ -48,6 +48,14 @@ public class PortMappingDO {
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* 协议
|
||||
*/
|
||||
private String protocal;
|
||||
/**
|
||||
* 子域名
|
||||
*/
|
||||
private String subdomain;
|
||||
/**
|
||||
* 服务端端口
|
||||
*/
|
||||
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
package org.dromara.neutrinoproxy.server.proxy.core;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.sun.deploy.net.proxy.ProxyType;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
|
||||
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.event.AppLoadEndEvent;
|
||||
import org.noear.solon.core.event.EventListener;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class HttpProxy implements EventListener<AppLoadEndEvent> {
|
||||
@Inject("serverBossGroup")
|
||||
private NioEventLoopGroup serverBossGroup;
|
||||
@Inject("serverWorkerGroup")
|
||||
private NioEventLoopGroup serverWorkerGroup;
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Override
|
||||
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
|
||||
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
|
||||
log.info("no config domain name,nonsupport http proxy.");
|
||||
return;
|
||||
}
|
||||
this.start();
|
||||
}
|
||||
|
||||
private void start() {
|
||||
try {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(serverBossGroup, serverWorkerGroup)
|
||||
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addFirst(new BytesMetricsHandler());
|
||||
ch.pipeline().addLast(new VisitorChannelHandler());
|
||||
}
|
||||
});
|
||||
bootstrap.bind("0.0.0.0", 80).sync();
|
||||
log.info("Http代理服务启动成功!");
|
||||
} catch (Exception e) {
|
||||
log.error("http proxy start err!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
|
||||
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
String host = getHost(byteBuf);
|
||||
if (StringUtils.isBlank(host)) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
log.debug("HttpProxy host: {}", host);
|
||||
if (!host.endsWith(proxyConfig.getServer().getDomainName())) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
int index = host.lastIndexOf("." + proxyConfig.getServer().getDomainName());
|
||||
String subdomain = host.substring(0, index);
|
||||
|
||||
// 根据域名拿到绑定的映射对应的cmdChannel
|
||||
Integer serverPort = ProxyUtil.getServerPortBySubdomain(subdomain);
|
||||
if (null == serverPort) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(serverPort);
|
||||
if (null == cmdChannel) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(serverPort);
|
||||
if (StringUtils.isBlank(lanInfo)) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
String visitorId = ProxyUtil.newVisitorId();
|
||||
Channel visitorChannel = ctx.channel();
|
||||
|
||||
byte[] bytes = new byte[byteBuf.readableBytes()];
|
||||
byteBuf.readBytes(bytes);
|
||||
ProxyAttachment proxyAttachment = new ProxyAttachment(ctx.channel(), bytes, (channel, buf) -> {
|
||||
Channel proxyChannel = channel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null == proxyChannel) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
|
||||
|
||||
// 增加流量计数
|
||||
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
|
||||
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
|
||||
});
|
||||
|
||||
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, serverPort);
|
||||
ProxyUtil.addProxyConnectAttachment(visitorId, proxyAttachment);
|
||||
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
|
||||
// 通知代理客户端
|
||||
Channel visitorChannel = ctx.channel();
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||
|
||||
if (cmdChannel == null) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
|
||||
// 用户连接断开,从控制连接中移除
|
||||
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
|
||||
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
|
||||
|
||||
// 删除代理附加对象
|
||||
ProxyUtil.remoteProxyConnectAttachment(visitorId);
|
||||
|
||||
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (proxyChannel != null && proxyChannel.isActive()) {
|
||||
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
|
||||
proxyChannel.attr(Constants.LICENSE_ID).remove();
|
||||
proxyChannel.attr(Constants.VISITOR_ID).remove();
|
||||
|
||||
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
// 通知客户端,用户连接已经断开
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId));
|
||||
}
|
||||
}
|
||||
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
// 当出现异常就关闭连接
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
private String getHost(ByteBuf byteBuf) {
|
||||
byte[] buf = new byte[byteBuf.readableBytes()];
|
||||
byteBuf.readBytes(buf);
|
||||
byteBuf.resetReaderIndex();
|
||||
String req = new String(buf);
|
||||
String[] lines = req.split("\r\n");
|
||||
String firstLine = lines[0];
|
||||
if (!(firstLine.endsWith("HTTP/1.1") || firstLine.endsWith("HTTP/1.0"))) {
|
||||
return null;
|
||||
}
|
||||
for (int i = 1; i < lines.length; i++) {
|
||||
String line = lines[i];
|
||||
if (!line.startsWith("Host: ")) {
|
||||
continue;
|
||||
}
|
||||
// 域名
|
||||
String domain = line.substring(6);
|
||||
return domain;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
-42
@@ -1,8 +1,10 @@
|
||||
package org.dromara.neutrinoproxy.server.proxy.core;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
|
||||
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
@@ -14,44 +16,42 @@ import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import org.noear.solon.Solon;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
|
||||
private static AtomicLong visitorIdProducer = new AtomicLong(0);
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
|
||||
// 当出现异常就关闭连接
|
||||
ctx.close();
|
||||
log.error("VisitorChannel error", cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
|
||||
|
||||
// 通知代理客户端
|
||||
Channel visitorChannel = ctx.channel();
|
||||
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (proxyChannel == null) {
|
||||
|
||||
if (null == proxyChannel) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
|
||||
|
||||
// 增加流量计数
|
||||
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
|
||||
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
|
||||
return;
|
||||
}
|
||||
// 转发代理数据
|
||||
byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
|
||||
|
||||
// 增加流量计数
|
||||
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
|
||||
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,23 +60,26 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||
|
||||
if (cmdChannel == null) {
|
||||
if (null == cmdChannel) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
String visitorId = newVisitorId();
|
||||
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort());
|
||||
if (StrUtil.isEmpty(lanInfo)) {
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
|
||||
visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
|
||||
|
||||
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, sa.getPort());
|
||||
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据代理服务端端口,获取被代理客户端局域网连接信息
|
||||
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort());
|
||||
if (StrUtil.isEmpty(lanInfo)) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
|
||||
visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
|
||||
|
||||
String visitorId = ProxyUtil.newVisitorId();
|
||||
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, sa.getPort());
|
||||
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
|
||||
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@@ -84,8 +87,8 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
|
||||
// 通知代理客户端
|
||||
Channel userChannel = ctx.channel();
|
||||
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
|
||||
Channel visitorChannel = ctx.channel();
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||
|
||||
if (cmdChannel == null) {
|
||||
@@ -95,10 +98,13 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
} else {
|
||||
|
||||
// 用户连接断开,从控制连接中移除
|
||||
String userId = ProxyUtil.getVisitorIdByChannel(userChannel);
|
||||
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, userId);
|
||||
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
|
||||
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
|
||||
|
||||
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
// 删除代理附加对象
|
||||
ProxyUtil.remoteProxyConnectAttachment(visitorId);
|
||||
|
||||
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (proxyChannel != null && proxyChannel.isActive()) {
|
||||
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
|
||||
proxyChannel.attr(Constants.LICENSE_ID).remove();
|
||||
@@ -106,7 +112,7 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
|
||||
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
// 通知客户端,用户连接已经断开
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(userId));
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,12 +140,4 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
super.channelWritabilityChanged(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为访问者连接产生ID
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static String newVisitorId() {
|
||||
return String.valueOf(visitorIdProducer.incrementAndGet());
|
||||
}
|
||||
}
|
||||
|
||||
-22
@@ -1,25 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.dromara.neutrinoproxy.server.proxy.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package org.dromara.neutrinoproxy.server.proxy.domain;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* 代理连接附件
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
public class ProxyAttachment {
|
||||
private Channel channel;
|
||||
private byte[] bytes;
|
||||
private BiConsumer<Channel, byte[]> executor;
|
||||
|
||||
public ProxyAttachment(Channel channel, byte[] bytes, BiConsumer<Channel, byte[]> executor) {
|
||||
this.channel = channel;
|
||||
this.bytes = bytes;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
if (null != executor) {
|
||||
this.executor.accept(channel, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-7
@@ -7,6 +7,7 @@ import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
|
||||
import org.dromara.neutrinoproxy.server.service.LicenseService;
|
||||
import org.dromara.neutrinoproxy.server.service.UserService;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
@@ -74,13 +75,20 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
}
|
||||
|
||||
Channel visitorChannel = ProxyUtil.getVisitorChannel(cmdChannel, visitorId);
|
||||
if (visitorChannel != null) {
|
||||
ctx.channel().attr(Constants.VISITOR_ID).set(visitorId);
|
||||
ctx.channel().attr(Constants.LICENSE_ID).set(licenseDO.getId());
|
||||
ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel);
|
||||
visitorChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
|
||||
// 代理客户端与后端服务器连接成功,修改用户连接为可读状态
|
||||
visitorChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
if (null == visitorChannel) {
|
||||
return;
|
||||
}
|
||||
ctx.channel().attr(Constants.VISITOR_ID).set(visitorId);
|
||||
ctx.channel().attr(Constants.LICENSE_ID).set(licenseDO.getId());
|
||||
ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel);
|
||||
visitorChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
|
||||
// 代理客户端与后端服务器连接成功,修改用户连接为可读状态
|
||||
visitorChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
|
||||
// 获取代理附加对象
|
||||
ProxyAttachment proxyAttachment = ProxyUtil.getProxyConnectAttachment(visitorId);
|
||||
if (null != proxyAttachment) {
|
||||
proxyAttachment.execute();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+51
-1
@@ -1,15 +1,19 @@
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingCreateReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingListReq;
|
||||
@@ -28,6 +32,7 @@ import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import ma.glasnost.orika.MapperFacade;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.Lifecycle;
|
||||
@@ -58,6 +63,8 @@ public class PortMappingService implements Lifecycle {
|
||||
|
||||
@Inject
|
||||
private PortPoolService portPoolService;
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
|
||||
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
|
||||
Page<PortMappingListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
@@ -90,6 +97,9 @@ public class PortMappingService implements Lifecycle {
|
||||
return;
|
||||
}
|
||||
item.setUserName(user.getName());
|
||||
if (StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(item.getSubdomain())) {
|
||||
item.setDomain(item.getSubdomain() + "." + proxyConfig.getServer().getDomainName());
|
||||
}
|
||||
});
|
||||
//sorted [userId asc] [licenseId asc] [createTime asc]
|
||||
respList = respList.stream().sorted(Comparator.comparing(PortMappingListRes::getUserId)
|
||||
@@ -109,11 +119,13 @@ public class PortMappingService implements Lifecycle {
|
||||
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
|
||||
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
|
||||
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), null), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
|
||||
|
||||
ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), null), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT);
|
||||
|
||||
Date now = new Date();
|
||||
PortMappingDO portMappingDO = new PortMappingDO();
|
||||
portMappingDO.setLicenseId(req.getLicenseId());
|
||||
portMappingDO.setProtocal(req.getProtocal());
|
||||
portMappingDO.setSubdomain(req.getSubdomain());
|
||||
portMappingDO.setServerPort(req.getServerPort());
|
||||
portMappingDO.setClientIp(req.getClientIp());
|
||||
portMappingDO.setClientPort(req.getClientPort());
|
||||
@@ -124,6 +136,12 @@ public class PortMappingService implements Lifecycle {
|
||||
portMappingMapper.insert(portMappingDO);
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
|
||||
// 更新域名映射
|
||||
if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) &&
|
||||
StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) &&
|
||||
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
||||
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
|
||||
}
|
||||
return new PortMappingCreateRes();
|
||||
}
|
||||
|
||||
@@ -137,6 +155,7 @@ public class PortMappingService implements Lifecycle {
|
||||
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
|
||||
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
|
||||
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
|
||||
ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT);
|
||||
|
||||
// 查询原端口映射
|
||||
PortMappingDO oldPortMappingDO = portMappingMapper.findById(req.getId());
|
||||
@@ -144,6 +163,8 @@ public class PortMappingService implements Lifecycle {
|
||||
|
||||
PortMappingDO portMappingDO = new PortMappingDO();
|
||||
portMappingDO.setId(req.getId());
|
||||
portMappingDO.setProtocal(req.getProtocal());
|
||||
portMappingDO.setSubdomain(req.getSubdomain());
|
||||
portMappingDO.setLicenseId(req.getLicenseId());
|
||||
portMappingDO.setServerPort(req.getServerPort());
|
||||
portMappingDO.setClientIp(req.getClientIp());
|
||||
@@ -153,6 +174,12 @@ public class PortMappingService implements Lifecycle {
|
||||
portMappingMapper.updateById(portMappingDO);
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO);
|
||||
// 更新域名映射
|
||||
if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) &&
|
||||
StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) &&
|
||||
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
||||
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
|
||||
}
|
||||
return new PortMappingUpdateRes();
|
||||
}
|
||||
|
||||
@@ -222,6 +249,11 @@ public class PortMappingService implements Lifecycle {
|
||||
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
|
||||
// 更新域名映射
|
||||
if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) &&
|
||||
StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
||||
ProxyUtil.removeSubdomainToServerPort(portMappingDO.getSubdomain());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,6 +271,24 @@ public class PortMappingService implements Lifecycle {
|
||||
@Override
|
||||
public void start() throws Throwable {
|
||||
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
|
||||
|
||||
// 未配置域名,则不需要处理域名映射逻辑
|
||||
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
|
||||
return;
|
||||
}
|
||||
List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>()
|
||||
.eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc())
|
||||
.isNotNull(PortMappingDO::getSubdomain)
|
||||
);
|
||||
if (CollectionUtil.isEmpty(portMappingDOList)) {
|
||||
return;
|
||||
}
|
||||
portMappingDOList.forEach(item -> {
|
||||
if (StrUtil.isBlank(item.getSubdomain())) {
|
||||
return;
|
||||
}
|
||||
ProxyUtil.setSubdomainToServerPort(item.getSubdomain(), item.getServerPort());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ProtocalService {
|
||||
|
||||
/**
|
||||
* 获取协议列表
|
||||
* @return
|
||||
*/
|
||||
public List<ProtocalListRes> list() {
|
||||
return Lists.newArrayList(
|
||||
new ProtocalListRes().setName("TCP").setEnable(Boolean.TRUE).setRemark("支持一切TCP之上的协议"),
|
||||
new ProtocalListRes().setName("HTTP").setEnable(Boolean.TRUE).setRemark("支持绑定子域名,未绑定时等价于时使用TCP"),
|
||||
new ProtocalListRes().setName("UDP").setEnable(Boolean.FALSE).setRemark("暂不支持")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
+75
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.dromara.neutrinoproxy.core.ChannelAttribute;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
|
||||
import io.netty.channel.Channel;
|
||||
@@ -12,6 +13,7 @@ import io.netty.util.AttributeKey;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
@@ -47,6 +49,18 @@ public class ProxyUtil {
|
||||
* cmdChannelAttachInfo.getUserChannelMap() 读写锁
|
||||
*/
|
||||
private static final ReadWriteLock userChannelMapLock = new ReentrantReadWriteLock();
|
||||
/**
|
||||
* 访问者ID生成器
|
||||
*/
|
||||
private static AtomicLong visitorIdProducer = new AtomicLong(0);
|
||||
/**
|
||||
* 代理 - connect附加映射
|
||||
*/
|
||||
private static Map<String, ProxyAttachment> proxyConnectAttachmentMap = new HashMap<>();
|
||||
/**
|
||||
* 子域名 - 服务端端口映射
|
||||
*/
|
||||
private static Map<String, Integer> subdomainToServerPort = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 初始化代理信息
|
||||
@@ -278,4 +292,65 @@ public class ProxyUtil {
|
||||
}
|
||||
return channel.attr(CHANNEL_ATTR_KEY).get().get("attachInfo");
|
||||
}
|
||||
|
||||
/**
|
||||
* 为访问者连接产生ID
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String newVisitorId() {
|
||||
return String.valueOf(visitorIdProducer.incrementAndGet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加代理附加对象
|
||||
* @param visitorId
|
||||
* @param proxyAttachment
|
||||
*/
|
||||
public static void addProxyConnectAttachment(String visitorId, ProxyAttachment proxyAttachment) {
|
||||
proxyConnectAttachmentMap.put(visitorId, proxyAttachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理附加对象
|
||||
* @param visitorId
|
||||
* @return
|
||||
*/
|
||||
public static ProxyAttachment getProxyConnectAttachment(String visitorId) {
|
||||
return proxyConnectAttachmentMap.get(visitorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除代理附加对象
|
||||
* @param visitorId
|
||||
*/
|
||||
public static void remoteProxyConnectAttachment(String visitorId) {
|
||||
proxyConnectAttachmentMap.remove(visitorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置子域名到服务端端口的映射
|
||||
* @param subdomain
|
||||
* @param serverPort
|
||||
*/
|
||||
public static void setSubdomainToServerPort(String subdomain, Integer serverPort) {
|
||||
subdomainToServerPort.put(subdomain, serverPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除子域名到服务端端口的映射
|
||||
* @param subdomain
|
||||
*/
|
||||
public static void removeSubdomainToServerPort(String subdomain) {
|
||||
subdomainToServerPort.remove(subdomain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据子域名获取外网端口
|
||||
* @param subdomain
|
||||
* @return
|
||||
*/
|
||||
public static Integer getServerPortBySubdomain(String subdomain) {
|
||||
return subdomainToServerPort.get(subdomain);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user