Nginx WebSocket 代理不断获得 HTTP 301 重定向

Dev*_*evy 4 python proxy curl nginx websocket

在过去的几天里,我一直试图让 Nginx WebSocket 代理工作,但在我的一生中,我无法让它工作。我按照官方指南here,一直使用 Python 的websockets模块作为服务器,使用 npm 包wscat作为客户端。wscat与 Python WebSocket 后端的直接连接工作正常(来自浏览器的连接也是如此)。但是一旦我在 Nginx 中分层,它就无法正常工作并继续给我一个标准的 HTTP 301 重定向。

使用 Nginx 代理的 cURL 调试输出:

$ curl 'http://test.ws:8080/websocket' \
> -H 'Pragma: no-cache' \
> -H 'Origin: http://localhost:8080' \
> -H 'Accept-Encoding: gzip, deflate, sdch' \
> -H 'Sec-WebSocket-Version: 13' \
> -H 'Sec-WebSocket-Key: V15bszpaQ+8Vq7mWR6NQbQ==' \
> -H 'User-Agent: Mozilla/5.0' \
> -H 'Upgrade: websocket' \
> -H 'Cache-Control: no-cache' \
> -H 'Connection: Upgrade' \
> -H 'Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits' \
    --compressed -v
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to test.ws (127.0.0.1) port 8080 (#0)
> GET /websocket HTTP/1.1
> Host: test.ws:8080
> Accept: */*
> Pragma: no-cache
> Origin: http://localhost:8080
> Accept-Encoding: gzip, deflate, sdch
> Sec-WebSocket-Version: 13
> Sec-WebSocket-Key: V15bszpaQ+8Vq7mWR6NQbQ==
> User-Agent: Mozilla/5.0
> Upgrade: websocket
> Cache-Control: no-cache
> Connection: Upgrade
> Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
> 
< HTTP/1.1 301 Moved Permanently
* Server nginx/1.8.0 is not blacklisted
< Server: nginx/1.8.0
< Date: Mon, 10 Aug 2015 15:04:26 GMT
< Content-Type: text/html
< Content-Length: 184
< Location: http://test.ws:8080/websocket/
< Connection: keep-alive
< 
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.8.0</center>
</body>
</html>
* Connection #0 to host test.ws left intact
Run Code Online (Sandbox Code Playgroud)

没有Nginx 代理的cURL 调试输出:

$ curl 'http://test.ws:8765/' \
> -H 'Pragma: no-cache' \
> -H 'Origin: http://localhost:8080' \
> -H 'Accept-Encoding: gzip, deflate, sdch' \
> -H 'Sec-WebSocket-Version: 13' \
> -H 'Sec-WebSocket-Key: V15bszpaQ+8Vq7mWR6NQbQ==' \
> -H 'User-Agent: Mozilla/5.0' \
> -H 'Upgrade: websocket' \
> -H 'Cache-Control: no-cache' \
> -H 'Connection: Upgrade' \
> -H 'Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits' \
> --compressed -v
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to test.ws (127.0.0.1) port 8765 (#0)
> GET / HTTP/1.1
> Host: test.ws:8765
> Accept: */*
> Pragma: no-cache
> Origin: http://localhost:8080
> Accept-Encoding: gzip, deflate, sdch
> Sec-WebSocket-Version: 13
> Sec-WebSocket-Key: V15bszpaQ+8Vq7mWR6NQbQ==
> User-Agent: Mozilla/5.0
> Upgrade: websocket
> Cache-Control: no-cache
> Connection: Upgrade
> Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
> 
< HTTP/1.1 101 Switching Protocols
* Server Python/3.4 websockets/2.5 is not blacklisted
< Server: Python/3.4 websockets/2.5
< Upgrade: WebSocket
< Connection: Upgrade
< Sec-WebSocket-Accept: yR97tmHAm9KPEI5vfKiM0/sfTqQ=
^C
Run Code Online (Sandbox Code Playgroud)

我的 Nginx(版本1.8.0)配置是这样的(websockets 在 test.ws (127.0.0.1) 的 8765 端口上运行),而 nginx 正在 localhost 上的 8080 端口上监听:

worker_processes  1;

error_log  logs/error.log debug;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    upstream ws {
        server 127.0.0.1:8765;
    }

    # map $http_upgrade $connection_upgrade {
    #     default upgrade;
    #     ''      close;
    # }

    server {
        listen       8080;
        server_name  test.ws;

        access_log  logs/localhost.access.log;

        location / {
            root   html;
            index  index.html index.htm;
        }

        location /websocket/ {
            proxy_pass http://ws;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

}

include servers/*;
Run Code Online (Sandbox Code Playgroud)

任何人都知道我在这里做错了什么,为什么它给了我 301 重定向?

Pee*_*201 5

在我的例子中,它来自使用路径 /websocket 而不是 /websocket/ 的客户端,从而阻止了位置规则的启动。但是位置顺序的影响是一个很好的提示!


Dev*_*evy 2

事实证明,我的 Nginx 配置中的根位置节一直在干扰 WebSocket 代理传递隧道。有三种方法可以解决这个问题。

  1. 修改根位置节以完全匹配。

    location = / {
        root   html;
        index  index.html index.htm;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 将根位置节重新排序为位置节之后websocket(首先是特定的,最后是一般的,就像重写匹配规则的工作方式一样)。

  3. 删除 Nginx 配置中的以下根位置节将使其正常工作。

    location / {
        root   html;
        index  index.html index.htm;
    }
    
    Run Code Online (Sandbox Code Playgroud)