无法使 websockets 通过 apache HTTPS 代理工作(302 错误)

无法使 websockets 通过 apache HTTPS 代理工作(302 错误)

我无法使用 apache 代理通过 HTTPS 连接到节点实例,从而使 websockets 在节点后端工作。如果没有使用 (apache) http(s) 代理,Websockets 可以正常工作。

我的设置:我有一个带有多个虚拟主机的 apache 服务器。我有一个用于 myserver.com 的 HTTPS 网页,并通过代理在 api.myserver.com 子域中具有 node/express/ws 的 HTTPS API,该代理将请求重定向到在端口 3333 上运行的 node.js 实例(PM2 上的多个实例)。

这是我的子域名的 apache 虚拟主机:

<VirtualHost *:443>
    ServerName api.myserver.com
    ServerAdmin [email protected]
    DocumentRoot /var/www/html/myserver/api
        Options -Indexes

    SSLEngine on                                                                
    SSLProtocol all -SSLv2                                                      
    SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM                

    SSLCertificateFile /etc/apache2/certs/STAR_myserver_co.crt
    SSLCertificateKeyFile /etc/apache2/certs/myserver_private_key.pem
    SSLCertificateChainFile /etc/apache2/certs/STAR_myserver_co.ca-bundle

    SSLProxyEngine On
    ProxyPreserveHost On
    ProxyRequests Off

    # This is for websocket requests
    ProxyPass /wss/ wss://localhost:3333/
    ProxyPassReverse /wss/ wss://localhost:3333/

    # This is for normal requests    
    ProxyPass / https://localhost:3333/
    ProxyPassReverse / https://localhost:3333/
</VirtualHost>

这对于将连接重定向到节点 Express 后端来说很有效。我已经安装了 mod_proxy、mod_proxy_http 和 mod_proxy_wstunnel。

这是 node.js API 后端:首先,我初始化 express、sessions 等。

// express, session and mongodb session storage
var express = require('express')
var session = require('express-session')
var MongoStore = require('connect-mongo')(session)
var app = express()
// configure sessionStore and sessions, mongodb, etc...

// Certificates and credentials for HTTPS server
var fs = require('fs')
var privateKey  = fs.readFileSync(__dirname + '/certs/myserver_private_key.pem', 'utf8')
var certificate = fs.readFileSync(__dirname + '/certs/myserver_cert.pem', 'utf8')
var ca = fs.readFileSync(__dirname + '/certs/myserver_ca.pem', 'utf8')
var credentials = {key: privateKey, cert: certificate, ca: ca}

app.enable('trust proxy')
app.set("trust proxy", 1)

然后我使用与 APACHE 相同的证书安全地设置了 HTTPS 服务器:

// Setup HTTPS server
var https = require('https')
var server = https.createServer(credentials, app)
server.listen(appPort, 'localhost', function () {
    // Server up and running!
    var host = server.address().address
    var port = server.address().port
    console.log('myserver listening at https://%s:%s', host, port)
})

最后,我设置了 websocket 连接:

// setup Websockets
wss = new WebSocketServer({ server: server })
wss.on('connection', function connection(ws) {
    var cookies = cookie.parse(ws.upgradeReq.headers.cookie)
    var sid = cookieParser.signedCookie(cookies["connect.sid"], myserver_secret)
    // extract user credentials and data from cookie/sid,

    // get the session object
    sessionStore.get(sid, function (err, ss) {
        ...
    })
})

然后我的客户端只是尝试安全地连接到 websockets(因为作为 HTTPS 应用程序,我不能使用 ws:// 不安全的 websockets 连接):

window.WebSocket = window.WebSocket || window.MozWebSocket
webSocket = new WebSocket('wss://' + location.host + '/wss')

然后我总是得到相同的错误 302:

[Error] WebSocket connection to 'wss://api.myserver.com/wss' failed: Unexpected response code: 302

如果我直接在本地服务器上测试节点实例https://本地主机:3333/它运行完美,并且 websockets 也能正常工作。

有什么办法可以解决这个问题吗?Apache 代理模块进行的 ws 重定向是否存在问题?

答案1

您已经在 Apache 上设置了 HTTPS,因此您已拥有从客户端到物理服务器的安全连接。您正在代理从 Apache 到本地主机上的 node.js 应用程序的连接,因此唯一可能的窃听者已经物理上在您的机器上。您已告诉您的应用程序信任代理app.enable('trust proxy'); app.set("trust proxy", 1);

那么为什么还要为 node.js 配置 HTTPS?我们使用 apache/nginx/haproxy 作为 Web 应用前端的主要原因之一是 TLS 卸载。

除了获取X-Forwarded-For包含远程客户端 IP 的标头之外,启用代理信任还可以让X-Forwarded-Proto您的 Express 应用知道连接是 http 还是 https。因此,Apache 和节点之间的 https 层除了给您的应用带来开销外,几乎没有什么作用。

我无法帮助您使用 Apache 代理 WebSockets。我开始使用 Apache 时,它​​还是一系列针对 httpd 的补丁。我在 WebSockets 推出之前就退出了,我不能冒险放弃。太多痛苦的回忆。如果您好奇,以下是我使用 haproxy 将 WebSockets 传递给我的一个 express 应用程序的方法:

mode      http
option    forwardfor
frontend http-in
bind :::80 v4v6
bind :::443 v4v6 ssl crt /etc/ssl/private
http-request  set-header X-Forwarded-Proto https if { ssl_fc }
http-request  set-header X-Forwarded-Port %[dst_port]
acl is_websocket hdr(Upgrade) -i WebSocket
acl is_websocket hdr_beg(Host) -i ws

redirect scheme https code 301 if !is_websocket !{ ssl_fc }
use_backend websocket_server if  is_websocket

backend websocket_server
timeout queue 5s
timeout server 86400s
timeout connect 86400s
server node_app 127.0.0.1:8080

backend ...others...

相关内容