Proxy

無法讓 websockets 通過 apache HTTPS 代理工作(302 錯誤)

  • August 30, 2017

我無法通過 HTTPS 使用 apache 代理在節點後端使 websockets 工作以連接到節點實例。如果不使用 (apache) http(s) 代理,Websocket 將正常工作。

我的設置:我有一個帶有多個虛擬主機的 apache 伺服器。我有一個用於 myserver.com 的 HTTPS 網頁以及通過代理在 api.myserver.com 子域中帶有 node/express/ws 的 HTTPS API,它將請求重定向到在埠 3333 上執行的 node.js 實例(PM2 上的多個實例) .

這是我的子域的 apache 虛擬主機:

<VirtualHost *:443>
   ServerName api.myserver.com
   ServerAdmin hello@myserver.com
   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>

這適用於將連接重定向到 node express 後端。我已經安裝了 mod_proxy、mod_proxy_http 和 mod_proxy_wstunnel。

這是 node.js API 後端:首先,我初始化 express、會話等。

// 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://localhost:3333/進行測試,它工作得很好,並且 websockets 可以正常工作。

知道如何解決這個問題嗎?Apache 代理模組進行的 ws 重定向是否存在問題?

您已經在 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 代理 WebSocket。當它仍然是 httpd 的一系列更新檔時,我開始使用 Apache。我在 WebSockets 出現之前就退出了,我不能冒險從馬車上掉下來。太多痛苦的回憶。如果您好奇,以下是我使用 haproxy 將 WebSockets 傳遞給我的一個快速應用程序的方法:

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...

引用自:https://serverfault.com/questions/814974