private static Publisher<Void> handleHttpRequest(
            BlocklistService blocklistService,
            HttpServerRequest request,
            HttpServerResponse response,
            ConnectionListener connectionListener,
            WebsocketServerSpec serverSpec) {
        // 1. Always respond with OK to CORS preflight requests
        if (isPreFlightRequest(request)) {
            return response.status(HttpResponseStatus.OK)
                    .addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
                    .addHeader(ACCESS_CONTROL_ALLOW_METHODS, "*")
                    .addHeader(ACCESS_CONTROL_ALLOW_HEADERS, "*")
                    .addHeader(ACCESS_CONTROL_MAX_AGE, "7200")
                    .send()
                    .then(Mono.never());
        }
        // 2. Validate handshake request
        HttpResponseStatus errorStatus = validateHandshakeRequest(request);
        if (errorStatus != null) {
            return response.status(errorStatus)
                    .send()
                    // Close the TCP connection
                    .then();
        }
        // 3. Get the real client address
        InetSocketAddress remoteAddress = request.remoteAddress();

        // 4. Check if the client address has been blocked
        if (remoteAddress != null
                && blocklistService.isIpBlocked(remoteAddress.getAddress()
                        .getAddress())) {
            return Mono.empty();
        }

        // 5. Upgrade to WebSocket
        // reactor.netty.http.server.HttpServer.HttpServerHandle.onStateChange
        int maxFramePayloadLength = serverSpec.maxFramePayloadLength();
        return response.sendWebsocket((in, out) -> {
            Flux<ByteBuf> inbound = in.aggregateFrames(maxFramePayloadLength)
                    .receiveFrames()
                    // Note that:
                    // 1. PingWebSocketFrame will be handled by Netty itself
                    // 2. The flatMap is called by FluxReceive, which will release buffer after
                    // "onNext" returns.
                    .flatMap(frame -> frame instanceof BinaryWebSocketFrame
                            ? Mono.just(frame.content())
                            : Mono.empty());
            Mono<Void> onClose = in.receiveCloseStatus()
                    .then();
            // BinaryWebSocketFrame will be created by
            // reactor.netty.http.server.WebsocketServerOperations.send
            Connection connection = (Connection) in;
            InetSocketAddress remoteAddr = remoteAddress == null
                    ? (InetSocketAddress) connection.channel()
                            .remoteAddress()
                    : remoteAddress;
            return connectionListener.onAdded(connection, remoteAddr, inbound, out, onClose);
        }, serverSpec);
    }
@implNote 1. Return MonoNever to keep the connection alive
          <p>
          2. Return MonoEmpty to close the connection.
turms-gateway/src/main/java/im/turms/gateway/access/client/websocket/WebSocketServerFactory.java