首页 > 解决方案 > 如何使用单个 Java ServerSocket 读取安全 Web 请求和处理 Web 套接字请求?

问题描述

介绍

我的代码可以处理发送到服务器的所有字节,并决定是否让它们通过并最终发送响应。我想用它来将服务器用作 Web 服务器、Web 套接字服务器和 tcp 服务器合二为一。

虽然我的代码是为 Minecraft 编写的,但我并不是在 Minecraft 论坛上问这个问题,因为回答这个问题不需要任何关于 Minecraft 或其代码库的先验知识。

所有你需要知道的关于我的世界

Minecraft 是一款可以在线玩的 Java 游戏。在线播放时,有一个服务器打开一个ServerSocket,所有玩家都有自己的客户端打开一个Socket,该Socket将与服务器的ServerSocket通信。

任何人都可以创建 Minecraft 服务器并在其服务器上安装服务器端修改(对于了解 Minecraft 的人来说,这些通常称为插件)。我的应用就是这样的服务器端修改。大多数 Minecraft 服务器由 Minecraft 托管公司托管。服务器的所有者对管理服务器文件的主机部分具有某种访问权限。

目标

我修改的目标是让 Minecraft 服务器服务于更多的客户端,而不仅仅是 Minecraft 客户端。我希望同一台服务器也可以用作 Web 服务器(用于 http 和 https 请求)以及(安全)Web 套接字服务器和 tcp 服务器。

为什么没有多个服务器套接字

最常见的解决方案是只为其他服务器类型创建一个 ServerSocket,并为它们分配一个不同的端口。但是,就我而言,这不是一个选择。大多数主机禁止您打开其他端口或要求额外的钱。所以我需要只用 Minecraft ServerSocket 来完成这一切。

我到目前为止所取得的成就

到目前为止,我已经设法让所有发送到我的世界服务器的字节首先通过我的代码。我的代码可以选择是否让字节继续到 Minecraft 服务器代码。它还可以自行发送响应,而无需通知 Minecraft 服务器代码。

原则上,我所做的足以实现我的目标,但我想要一些关于如何继续的帮助。我将在下面解释到目前为止我已经完成和尚未完成的事情。

Minecraft 客户端向服务器发送的第一个字节总是相同的,即 16。这很棒,因为它让我可以轻松地将 Minecraft 客户端与 Web 浏览器和 tcp 客户端区分开来。

HTTP 请求和 websocket 连接总是以相同的字节开始,即 71。HTTPS 和安全 websocket 总是以字节 22 开始。我所说的 TCP 连接将由我自己的应用程序发送,所以我可以准确地选择它们将发送的字节发送,我可以简单地编写我的修改来响应它。

我设法通过它们的连接属性来区分 http 请求和 websocket 连接。Http 请求总是发送“Connection: keep-alive”,而 websocket 连接总是发送“Connection: upgrade”。(虽然有些浏览器会使用大写的 k、a 和 u,而其他浏览器则不会。)

处理正常的 http 请求并不难。处理 TCP 连接也不难,因为我将控制一切。但我对剩余的连接类型有疑问:

我需要帮助的问题

Web 套接字协议非常大,我不希望只用我的代码来完全处理它。(我之前尝试过这个,但我一直遇到很少使用的部分问题,因此没有测试。)所以我想使用一些库,让我只担心有效负载而不是整个协议。不幸的是,Web 套接字库通常希望创建 ServerSocket,这在我的情况下是不可能的。那么有人对在这里做什么有建议吗?

我还没有找到有关如何正确读取 https 请求的任何信息。有人可以告诉我在哪里可以找到该协议的详细信息或提供一个不错的链接吗? 对于安全的网络套接字,在我了解如何读取请求后,我将面临与“正常”网络套接字连接相同的问题。

代码

到目前为止,我的所有代码都可以在https://github.com/knokko/Multi-Purpose-Server找到。最有趣的部分可能是我的代码有机会在所有字节到达 Minecraft 代码之前处理它们的部分,该代码如下所示。

简短的问题

对于那些不完全理解我的问题的人(您可以将其视为两个密切相关的问题)是:

-我应该如何阅读 https 请求和安全的网络套接字握手?

- 有谁知道可以处理不需要创建 ServerSocket 本身的 Web 套接字输入的库?

// This channel handler will be registered for every connection client that will
                    // inspect
                    // any message before it reaches the Minecraft code.
                    pipeline.addFirst("multipurpose_handler_inspector", new ChannelInboundHandlerAdapter() {

                        private boolean deactivated;

                        private ChannelListener listener;

                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        //super.channelRead will send the content to the minecraft code
                            try {
                                if (!deactivated) {
                                    ByteBuf message = (ByteBuf) msg;
                                    if (listener != null) {
                                        listener.read(ctx, message);
                                    } else {
                                        byte firstByte = message.getByte(0);

                                        // All Minecraft connections start with the byte 16
                                        if (firstByte == 16) {
                                            deactivated = true;
                                            super.channelRead(ctx, msg);
                                        }

                                        // All insecure web connections start with the byte 71
                                        else if (firstByte == 71) {
                                            byte[] data = new byte[message.readableBytes()];
                                            message.getBytes(0, data);
                                            WebHandler.Type type = WebHandler.determineConnectionType(data);
                                            if (type == WebHandler.Type.HTTP) {
                                                listener = new HTTPListener();
                                                listener.readInitial(ctx, message);
                                            } else if (type == WebHandler.Type.WEBSOCKET) {
                                                // TODO Find a nice way to handle web socket connections
                                                listener = new WebSocketListener();
                                                listener.readInitial(ctx, message);
                                            } else {
                                                deactivated = true;
                                                super.channelRead(ctx, msg);
                                            }
                                        }

                                        // All secure web connections start with the byte 22
                                        else if (firstByte == 22) {
                                            // TODO implement the secure web protocols and find a way to read this stuff
                                            // and find the difference
                                            System.out.println(
                                                    "We are dealing with a secure websocket or https connection");
                                            byte[] data = new byte[message.readableBytes()];
                                            message.getBytes(0, data);
                                            System.out.println(new String(data));
                                        }

                                        // My applications
                                        else if (firstByte == 31) {
                                            listener = new TCPListener();
                                            listener.readInitial(ctx, message);
                                        } else {
                                            System.out.println("Unknown connection type");
                                            deactivated = true;
                                            super.channelRead(ctx, msg);
                                        }
                                    }
                                } else {
                                    super.channelRead(ctx, msg);
                                }
                            } catch (Exception ex) {
                                ex.printStackTrace();
                            }
                        }
                    });

标签: javaserver

解决方案


如果您始终可以识别 Minecraft 流量,那么您最好的选择可能是在同一个盒子上运行 apache/httpd 和/或 tomcat 服务器并将所有非 Minecraft 流量转发给它。如果你这样做,HTTPS 的东西可能只是为 https 流量正确配置 http 服务器的问题。

您可能必须将您的代码配置为 http 代理——事实上(只是想到这一点)您可能想出去寻找一个开源 http 代理,然后用您的代码对其进行调整以提取 Minecraft 流量并在之前转发它做其余的代理工作。

我不会从头开始做 HTTPS 的东西,这不是很困难,但我认为这不是微不足道的。

哦,如果您的问题是“将 Minecraft HTTPS 流量与同一端口上的其他 HTTPS 连接区分开来”,我只能说这可能是您问题的一个好主题:)


推荐阅读