springboot集成netty实现代理服务器

说明

使用netty实现代理服务功能,思路是:客户端发送请求,由netty服务端通过端口监听到请求,然后在内部再开启一个netty客户端作为代理去访问真实的服务器,最后由真实的服务器将响应返回给代理,代理再返回给netty服务端,最后返回给浏览器。
在这里插入图片描述
目前实现了http和https的代理。

导入依赖

<dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-buffer</artifactId>
            <version>${netty.version}</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-codec</artifactId>
            <version>${netty.version}</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-codec-http</artifactId>
            <version>${netty.version}</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-handler</artifactId>
            <version>${netty.version}</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-handler-proxy</artifactId>
            <version>${netty.version}</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>1.58</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

配置

websync.port=9999

容器加载后启动程序

@Component
public class Runner implements ApplicationRunner {

    @Value("${websync.port}")
    private int port;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        new Server(port).start();
    }

}

服务端

public class Server {
    public final static HttpResponseStatus SUCCESS = new HttpResponseStatus(200,
            "Connection established");
    private final int PORT;
    private final EventLoopGroup workerStateEvent = new NioEventLoopGroup();
    private final EventLoopGroup bossStateEvent = new NioEventLoopGroup();
    private final ServerBootstrap bootstrap = new ServerBootstrap();
    private final ServerHandler serverHandler = new ServerHandler();

    public Server(int PORT) {
        this.PORT = PORT;
    }

    public void start() throws InterruptedException {
        bootstrap.group(bossStateEvent, workerStateEvent)
                .channel(NioServerSocketChannel.class)
                .localAddress(new InetSocketAddress(PORT))
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast("httpCodec", new HttpServerCodec());
                        socketChannel.pipeline().addLast("httpObject", new HttpObjectAggregator(65536));
                        socketChannel.pipeline().addLast(serverHandler);
                    }
                });

        ChannelFuture channel = bootstrap.bind().sync();
        //关闭通道
        channel.channel().closeFuture().sync();
    }

}

服务端handler

//线程间共享,但必须要保证此类线程安全
@ChannelHandler.Sharable
public class ServerHandler extends ChannelInboundHandlerAdapter {

    private final static Log LOG = LogFactory.getLog(ServerHandler.class);


    //保证线程安全
    private ThreadLocal<ChannelFuture> futureThreadLocal = new ThreadLocal<>();
    private final AtomicInteger PORT = new AtomicInteger(0);
    private final AtomicReference<String> HOST = new AtomicReference<String>("0.0.0.0");


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        LOG.info("服务器连接成功......");
    }

    @Override
    public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
        //http
        if (msg instanceof FullHttpRequest) {
            FullHttpRequest request = (FullHttpRequest) msg;
            String name = request.method().name();
            RequestProto protoUtil = ProtoUtil.getRequestProto(request);
            String host = protoUtil.getHost();
            int port = protoUtil.getPort();
            PORT.set(port);
            HOST.set(host);
            request.headers().set("11", "222");
            if ("CONNECT".equalsIgnoreCase(name)) {//HTTPS建立代理握手
                HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, Server.SUCCESS);
                ctx.writeAndFlush(response);
                ctx.pipeline().remove("httpCodec");
                ctx.pipeline().remove("httpObject");
                return;
            }
            //开启代理服务器
            new ProxyServer(host, port, msg, ctx.channel()).start();
        } else { //https,只转发数据,不对数据做处理,所以不需要解密密文
            ChannelFuture future = futureThreadLocal.get();
            //代理连接还未建立
            if (future == null) {
                //连接至目标服务器
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(ctx.channel().eventLoop()) // 复用客户端连接线程池
                        .channel(ctx.channel().getClass()) // 使用NioSocketChannel来作为连接用的channel类
                        .handler(new ChannelInitializer() {
                            @Override
                            protected void initChannel(Channel ch) throws Exception {
                                ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx0, Object msg) throws Exception {
                                        ctx.channel().writeAndFlush(msg);
                                    }

                                    @Override
                                    public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                        System.out.println("https 代理服务器连接成功...");
                                    }
                                });
                            }
                        });
                future = bootstrap.connect(HOST.get(), PORT.get());
                futureThreadLocal.set(future);
                future.addListener(new ChannelFutureListener() {
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (future.isSuccess()) {
                            future.channel().writeAndFlush(msg);
                        } else {
                            ctx.channel().close();
                        }
                    }
                });
            } else {
                //代理建立连接之后,直接刷回数据
                future.channel().writeAndFlush(msg);
            }
        }

    }
}

代理客户端

public class ProxyServer {


    private final String HOST;
    private final int PORT;
    private final Object msg;
    private final Channel channel;

    public ProxyServer(String HOST, int PORT, Object msg, Channel channel) {
        this.HOST = HOST;
        this.PORT = PORT;
        this.msg = msg;
        this.channel = channel;
    }


    public void start() {
        Bootstrap bootstrap = new Bootstrap();
        EventLoopGroup group = new NioEventLoopGroup();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new HttpClientCodec());
                        socketChannel.pipeline().addLast(new HttpObjectAggregator(6553600));
                        socketChannel.pipeline().addLast(new ProxyServerHandler(channel));
                    }
                })
                .connect(new InetSocketAddress(HOST, PORT))
                .addListener(new ChannelFutureListener() {
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (future.isSuccess()) {
                            HeaderUtil.addHeaders(future, msg);
                        } else {
                            future.channel().close();
                        }
                    }
                });
    }


}

代理handler

public class ProxyServerHandler extends ChannelInboundHandlerAdapter {

    private Channel channel;

    public ProxyServerHandler(Channel channel) {
        this.channel = channel;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("代理服务器连接成功.....");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        channel.writeAndFlush(msg);
    }
}

工具类

public class HeaderUtil {
    /**
     * @methodName: addHeaders
     * @description: 添加headers信息,响应客户端
     * @auther: CemB
     * @date: 2018/12/20 17:18
     */
    public static void addHeaders(ChannelFuture future, Object request) {
        if (request instanceof HttpRequest) {
            HttpRequest msg = (FullHttpRequest) request;
            msg.headers().set("111", "222");
            future.channel().writeAndFlush(msg);
        } else {
            future.channel().writeAndFlush(request);
        }
    }
}
public class ProtoUtil {

    public static RequestProto getRequestProto(HttpRequest httpRequest) {
        RequestProto requestProto = new RequestProto();
        int port = -1;
        String hostStr = httpRequest.headers().get(HttpHeaderNames.HOST);
        if (hostStr == null) {
            Pattern pattern = Pattern.compile("^(?:https?://)?(?<host>[^/]*)/?.*$");
            Matcher matcher = pattern.matcher(httpRequest.uri());
            if (matcher.find()) {
                hostStr = matcher.group("host");
            } else {
                return null;
            }
        }
        String uriStr = httpRequest.uri();
        Pattern pattern = Pattern.compile("^(?:https?://)?(?<host>[^:]*)(?::(?<port>\\d+))?(/.*)?$");
        Matcher matcher = pattern.matcher(hostStr);
        //先从host上取端口号没取到再从uri上取端口号 issues#4
        String portTemp = null;
        if (matcher.find()) {
            requestProto.setHost(matcher.group("host"));
            portTemp = matcher.group("port");
            if (portTemp == null) {
                matcher = pattern.matcher(uriStr);
                if (matcher.find()) {
                    portTemp = matcher.group("port");
                }
            }
        }
        if (portTemp != null) {
            port = Integer.parseInt(portTemp);
        }
        boolean isSsl = uriStr.indexOf("https") == 0 || hostStr.indexOf("https") == 0;
        if (port == -1) {
            if (isSsl) {
                port = 443;
            } else {
                port = 80;
            }
        }
        requestProto.setPort(port);
        requestProto.setSsl(isSsl);
        return requestProto;
    }

    public static class RequestProto implements Serializable {

        private static final long serialVersionUID = -6471051659605127698L;
        private String host;
        private int port;
        private boolean ssl;

        public RequestProto() {
        }

        public RequestProto(String host, int port, boolean ssl) {
            this.host = host;
            this.port = port;
            this.ssl = ssl;
        }

        public String getHost() {
            return host;
        }

        public void setHost(String host) {
            this.host = host;
        }

        public int getPort() {
            return port;
        }

        public void setPort(int port) {
            this.port = port;
        }

        public boolean getSsl() {
            return ssl;
        }

        public void setSsl(boolean ssl) {
            this.ssl = ssl;
        }
    }
}
  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Spring Boot集成Netty可以实现TCP协议的通信。Netty是一个高性能、异步事件驱动的网络应用框架,可以用于开发各种协议的服务器和客户端。在Spring Boot中,可以通过添加Netty依赖和配置Netty的相关参数来实现TCP通信。具体实现方式可以参考相关文档和示例代码。 ### 回答2: Spring Boot是一种快速开发框架,旨在简化应用程序的开发和部署过程。Netty是一个事件驱动的网络应用程序框架。它允许您快速构建高性能和可扩展的网络应用程序。在本文中,我们将探讨如何使用Spring Boot集成Netty实现TCP。 首先,我们需要在pom.xml文件中添加Netty和Spring Boot的依赖。这有助于我们在代码中使用Spring Boot和Netty的类和方法。例如,我们需要添加以下依赖: ```xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.57.Final</version> </dependency> </dependencies> ``` 接下来,我们将创建一个Netty服务器类来处理传入的TCP连接。以下是一个简单的Netty服务器类: ```java @Component public class NettyServer { private final int port; @Autowired public NettyServer(@Value("${netty.port:8000}") int port) { this.port = port; } public void start() throws InterruptedException { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new StringDecoder(), new StringEncoder(), new NettyServerHandler()); } }) .option(ChannelOption.SO_BACKLOG, 100) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(port).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } } ``` 在此代码中,我们创建了一个Netty服务器来处理传入的TCP连接。我们使用@Value注释将端口号设置为默认值8000。在start()方法中,我们创建了两个事件循环组,一个用于接受传入连接,另一个用于处理连接。我们还将服务器绑定到指定的端口,并在服务器关闭时优雅地关闭事件循环组。 接下来,我们需要创建一个Netty处理程序类来处理传入的数据。以下是一个简单的Netty处理程序类: ```java @Component public class NettyServerHandler extends SimpleChannelInboundHandler<String> { @Override public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("Received message: " + msg); ctx.write(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } ``` 在这个代码中,我们创建了一个Netty服务器处理程序类来处理传入的数据。我们使用SimpleChannelInboundHandler类来处理数据。在channelRead0()方法中,我们打印接收到的消息并将消息写回客户端。在channelReadComplete()方法中,我们将缓冲区的所有数据刷新到远程节点并关闭通道。在exceptionCaught()方法中,我们打印了异常堆栈并关闭通道。 最后,我们需要创建一个Spring Boot应用程序类来启动Netty服务器。以下是一个简单的Spring Boot应用程序类: ```java @SpringBootApplication public class Application { public static void main(String[] args) throws InterruptedException { SpringApplication.run(Application.class, args); NettyServer server = new NettyServer(8000); server.start(); } } ``` 在此代码中,我们启动了Spring Boot应用程序并创建了一个Netty服务器对象。我们在启动服务器时将端口号设置为8000。注意,我们使用的是阻塞式服务器,并且它会阻塞主线程,直到服务器被关闭。 综上所述,我们使用Spring Boot集成Netty实现了TCP。我们创建了一个Netty服务器类来处理传入的TCP连接,一个Netty处理程序类来处理传入的数据,最后创建了一个Spring Boot应用程序类来启动Netty服务器。 ### 回答3: Spring Boot是一套快速开发框架,它可以帮助开发人员更快速、更高效地开发应用程序。而Netty是一个高性能的网络框架,可以实现异步、事件驱动的网络编程。将Spring Boot和Netty结合在一起,可以实现快速构建高性能的网络应用程序。 想要将Spring Boot集成Netty实现TCP,有以下几个步骤: 1. 添加Netty依赖:在pom.xml中添加Netty的依赖,可以从官网上找到最新版本的依赖。 2. 编写Netty处理器:首先需要编写一个Netty的处理器类,来处理客户端的请求。处理器类需要实现Netty的ChannelInboundHandler接口,并重写其中的channelRead方法,在该方法中处理来自客户端的数据。 3. 配置Netty服务器:在程序的入口类中,创建Netty服务器并配置相关参数,如端口号、主机地址等。同时,需要将编写的处理器类设置进来,以便在接收到客户端的请求时能够正确调用。 4. 运行程序:配置好之后,可以运行程序,测试TCP协议的功能是否正常。此时可以使用各种工具进行测试,如Telnet、Putty等。 综上所述,使用Spring Boot集成Netty实现TCP,可以大大简化应用程序的开发流程,同时也能够实现高性能的网络编程。如果开发人员想要实现更复杂的网络应用程序,可以在Netty处理器中添加更多的功能逻辑,并根据具体的业务需求进行开发。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值