在Node.js中将WebSocket请求从https代理到http

uelo1irk  于 2023-08-04  发布在  Node.js
关注(0)|答案(2)|浏览(189)

我正在使用Node.js代理服务器,它将来自网站的请求路由到本地主机端点。网站可以在本地或云示例上运行,因此代理需要支持HTTP和HTTPS请求并将它们路由到HTTP端点。
下面的代码几乎适用于所有用例,除了WebSocket请求来自HTTPS地址时。代码如下:

import * as express from "express";
import { Server, createServer } from "http";
import { createProxyServer } from "http-proxy";
import { settings } from "./settings"

export let port = 3004;

let server: Server;

export function startServer() {
    const URL = settings.URL;
    const app = express();
    server = createServer(app);

    const proxy = createProxyServer({
        target: URL,
        changeOrigin: !URL.includes("localhost"),
        ws: true,
        headers: {
            // Attach request headers
        },
    });

    server.on("upgrade", function (req, socket, head) {
        proxy.ws(req, socket, head, {});
    });

    app.get("/public/*", function (req, res) {
        proxy.web(req, res, {});
    });

    app.get("/api/*", function (req, res) {
        proxy.web(req, res, {});
    });

    app.post("/api/query", function (req, res) {
        proxy.web(req, res, {});
    });

    server.listen(0, () => {
        port = server?.address()?.port;
        console.log("Server started");
    });
}

export function stopServer() {
    if (server) {
        server.close();
    }
}

字符串
changeOrigin: !URL.includes("localhost")根据主机设置changeOrigin。它对于localhost请求是不必要的,但对于HTTPS请求是必需的。
然而,这段代码对于WebSocket请求失败,并返回以下错误:

WebSocket connection to 'ws://localhost:61958/api/ws' failed:
//...

ERR read ECONNRESET: Error: read ECONNRESET
   at TCP.onStreamRead (node:internal/stream_base_commons:217:20)
   at TCP.callbackTrampoline (node:internal/async_hooks:130:17)


如果我没有通过以下方式为WebSocket端点设置changeOrigin

server.on("upgrade", function (req, socket, head) {
    proxy.ws(req, socket, head, {changeOrigin: false});
  });


我得到一个不同的错误:

ERR Client network socket disconnected before secure TLS connection was established:
Error: Client network socket disconnected before secure TLS connection was established
  at connResetException (node:internal/errors:704:14)
  at TLSSocket.onConnectEnd (node:_tls_wrap:1590:19)
  at TLSSocket.emit (node:events:525:35)
  at endReadableNT (node:internal/streams/readable:1358:12)
  at process.processTicksAndRejections (node:internal/process/task_queues:83:21)


有什么解决办法吗?我觉得我忽略了一些简单的东西,但无法弄清楚。

ukxgm1gy

ukxgm1gy1#

Ruslan Zhomiranswer提到了一个解决方法:使用http-party/node-http-proxy中使用的proxyReq事件修改WebSocket升级请求中的origin头。
这可能适用于目标端点使用origin头来验证WebSocket连接的场景,但这不是常见的要求。如果你只需要处理简单的、不安全的WebSocket连接,这可能就足够了。
但是,如果您需要处理安全和不安全WebSocket连接的混合,或者如果您正在开发生产级应用程序,则需要不同的方法。

问题分析

现在,您正在尝试将安全的WebSocket升级请求从HTTPS客户端直接传递到HTTP目标,而没有正确管理和解密这些安全请求。您需要向代理服务器添加适当的SSL/TLS处理来解决这个问题。
在您的原始代码中,您正在管理upgrade事件并使用proxy.ws方法,类似于node-http-proxy自述文件。

server.on("upgrade", function (req, socket, head) {
    proxy.ws(req, socket, head, {});
});

字符串
当客户端建立WebSocket连接时,它首先发送一个普通的HTTP请求,然后请求将连接升级到WebSocket。在您的情况下,如果此初始请求通过HTTPS发送,则将连接升级到WebSocket的请求 * 也 * 通过HTTPS发送。
但是,在上面的代码中,您试图将此升级请求直接传递给代理目标。如果目标是HTTP端点,它将无法处理来自HTTPS客户端的加密请求,导致您看到的ECONNRESET错误。
当前的代理设置也无法正确处理HTTPS WebSocket连接:

const proxy = createProxyServer({
    target: URL,
    changeOrigin: !URL.includes("localhost"),
    ws: true,
    headers: {
        // Attach request headers
    },
});


在这里,您将ws选项设置为true,表示代理应该能够处理WebSocket请求。但是,由于您没有提供任何SSL/TLS选项来处理HTTPS,因此代理无法正确建立 * 安全 * WebSocket连接。这就是第二个错误(关于在建立安全TLS连接之前断开连接)的来源:您尝试将WebSocket请求从HTTPS客户端代理到HTTP服务器。
当WebSocket请求来自HTTPS客户端时,客户端期望建立安全的WebSocket连接(wss://)。为了正确地接受这个安全连接并将其降级为HTTP目标的非安全连接,您的代理服务器需要设置为处理HTTPS。否则,它将无法正确建立客户端所期望的安全WebSocket连接。
您的代理服务器需要首先正确建立HTTPS WebSocket连接,然后将其降级为目标的非安全WebSocket连接。

可能的解决方案

由于您的目标始终是HTTP端点,并且您的服务器可以接收HTTP或HTTPS,因此您应该单独处理HTTPS连接,然后将其传递给代理。
这意味着除了HTTP服务器之外还要创建一个HTTPS服务器。您的HTTPS服务器应该具有所有必要的SSL/TLS配置,并且它可以简单地将请求转发到HTTP服务器。可以为此创建一个简单的自签名证书。
例如:

import { createServer, Server } from "http";
import { createServer as createHttpsServer } from "https";
import * as express from "express";
import { createProxyServer } from "http-proxy";
import * as fs from "fs";
import * as https from "https";
import { settings } from "./settings"

export let port = 3004;

let server: Server;

export function startServer() {
    const URL = settings.URL;
    const app = express();
    server = createServer(app);
    
    const options = {
        key: fs.readFileSync('key.pem'),
        cert: fs.readFileSync('cert.pem')
    };

    const httpsServer = createHttpsServer(options, app);

    const proxy = createProxyServer({
        target: URL,
        changeOrigin: !URL.includes("localhost"),
        ws: true,
        headers: {
            // Attach request headers
        },
    });

    httpsServer.on("upgrade", function (req, socket, head) {
        proxy.ws(req, socket, head, {});
    });

    server.on("upgrade", function (req, socket, head) {
        proxy.ws(req, socket, head, {});
    });

    app.get("/public/*", function (req, res) {
        proxy.web(req, res, {});
    });

    app.get("/api/*", function (req, res) {
        proxy.web(req, res, {});
    });

    app.post("/api/query", function (req, res) {
        proxy.web(req, res, {});
    });

    server.listen(0, () => {
        port = server?.address()?.port;
        console.log("HTTP Server started");
    });

    httpsServer.listen(8443, () => {
        console.log("HTTPS Server started on port 8443");
    });
}

export function stopServer() {
    if (server) {
        server.close();
    }
}


这段代码创建了一个HTTP服务器和一个HTTPS服务器,它们监听不同的端口(3004/8443)。HTTPS服务器正在使用key.pemcert.pem进行SSL/TLS配置。它处理HTTPS WebSocket升级请求,而HTTP服务器处理HTTP WebSocket升级请求。
HTTPS服务器不会显式解码传入的安全WebSocket升级请求。相反,它侦听这些安全的WebSocket请求并代理它们,利用node-http-proxy library的能力来处理WebSocket连接。

const httpsServer = createHttpsServer(options, app);
    
httpsServer.on("upgrade", function (req, socket, head) {
    proxy.ws(req, socket, head, {});
});


使用提供的SSL/TLS选项创建单独的HTTPS服务器。该服务器侦听“upgrade”事件,当客户端想要发起WebSocket连接时发生该事件。
当接收到“upgrade“事件时,它调用proxy.ws(),如node-http-proxy/ UPGRADING中所示,并处理将WebSocket请求代理到创建代理时指定的目标的过程。
在一些日志记录之后,似乎从未调用httpsServer的升级事件,只有来自httpServer的事件。
我在控制台中也看到了这个错误:transport_websocket.js:32 WebSocket connection to 'ws://localhost:8443' failed .
因此,客户端试图建立一个到安全服务器(localhost:8443)的非安全WebSocket连接(ws://)。这将不起作用,因为localhost:8443服务器设置为仅接受安全WebSocket(wss://)连接,因为存在SSL/TLS配置。
您需要修改客户端以启动安全的WebSocket连接。如果客户端是Web浏览器,则在创建新的WebSocket连接时应使用wss://localhost:8443而不是ws://localhost:8443
应根据连接是否需要安全使用正确的协议(ws://用于HTTP,wss://用于HTTPS)。

fafcakar

fafcakar2#

要正确处理HTTP和HTTPS请求的WebSocket代理,您需要修改Node.js代理服务器以支持WebSocket连接的SSL/TLS。您可以通过使用Node.js中的'http'和'https'模块来创建单独的HTTP和HTTPS服务器来实现这一点,每个服务器都有各自的配置。此外,您应该修改客户端WebSocket连接以使用正确的协议(对于HTTP为ws://,对于HTTPS为wss://)。
以下是Node.js代理服务器的更新代码:

import * as express from "express";
import { Server as HttpServer, createServer } from "http";
import { Server as HttpsServer, createServer as createHttpsServer } from "https";
import { createProxyServer } from "http-proxy";
import * as fs from "fs";
import { settings } from "./settings";

export let httpPort = 3004;
export let httpsPort = 8443;

let httpServer: HttpServer;
let httpsServer: HttpsServer;

export function startServer() {
  const URL = settings.URL;
  const app = express();

  // SSL/TLS configurations for HTTPS server
  const options = {
    key: fs.readFileSync("key.pem"),
    cert: fs.readFileSync("cert.pem"),
  };

  httpServer = createServer(app);
  httpsServer = createHttpsServer(options, app);

  const httpProxy = createProxyServer({
    target: URL,
    changeOrigin: !URL.includes("localhost"),
    ws: true,
  });

  const httpsProxy = createProxyServer({
    target: URL,
    changeOrigin: !URL.includes("localhost"),
    ws: true,
    secure: false, // Allow proxying to a non-secure WebSocket server
  });

  httpServer.on("upgrade", function (req, socket, head) {
    httpProxy.ws(req, socket, head, {});
  });

  httpsServer.on("upgrade", function (req, socket, head) {
    httpsProxy.ws(req, socket, head, {});
  });

  app.get("/public/*", function (req, res) {
    httpProxy.web(req, res, {});
  });

  app.get("/api/*", function (req, res) {
    httpProxy.web(req, res, {});
  });

  app.post("/api/query", function (req, res) {
    httpProxy.web(req, res, {});
  });

  httpServer.listen(httpPort, () => {
    httpPort = httpServer?.address()?.port;
    console.log("HTTP Server started on port", httpPort);
  });

  httpsServer.listen(httpsPort, () => {
    httpsPort = httpsServer?.address()?.port;
    console.log("HTTPS Server started on port", httpsPort);
  });
}

export function stopServer() {
  if (httpServer) {
    httpServer.close();
  }
  if (httpsServer) {
    httpsServer.close();
  }
}

字符串
代码中的更改:
1.代码现在使用'http'和'https'模块来创建单独的HTTP和HTTPS服务器。HTTPS服务器配置了SSL/TLS选项,允许它处理安全连接。
1.两个独立的代理示例httpProxyhttpsProxy分别为HTTP和HTTPS服务器创建。httpsProxy示例将secure: false设置为允许代理非安全WebSocket服务器(HTTP)。

  1. HTTP服务器侦听端口3004,HTTPS服务器侦听端口8443。
    1.在startServer()函数中,WebSocket升级事件现在由各自的服务器正确处理(httpServer用于HTTP,httpsServer用于HTTPS)。每个服务器使用其对应的代理示例来处理WebSocket连接。
    现在,在客户端,如果您使用的是Web浏览器,那么在连接到代理服务器时,您应该启动一个安全的WebSocket连接(wss://):
const webSocket = new WebSocket("wss://localhost:8443/api/ws");


如果你需要通过不安全的WebSocket连接(ws://)连接到代理服务器,你可以这样做:

const webSocket = new WebSocket("ws://localhost:3004/api/ws");


使用此更新的代码和正确的客户端WebSocket连接,您的Node.js代理服务器现在将能够处理安全(wss://)和不安全(ws://)WebSocket连接,并将它们正确转发到适当的目标(HTTP端点)。

相关问题