最近需要用到Nginx反向代理Websocket协议,虽然Nginx配置起来了就两行配置但还是有必要来了解下它的原理。

HTTP Upgrade

HTTP/1.1协议提出的一种协议升级机制,允许将一个已经建立的HTTP连接升级到其它的协议。当客户端试图升级到新的协议时,需要发一个带有额外两个header的请求:

1
2
  Connection: Upgrade
  Protocol: protocol-name ["/" protocol-version]

Connection: Upgrade表示我想升级协议,Protocol指出我想升级成的协议和版本,如果服务端同意升级需要以101状态码响应客户端的请求。

1
2
3
HTTP/1.1 101 Switching Protocols
Connection: upgrade
Upgrade: HTTP/2.0

到次就可以用新协议通信了。

Websocket建连过程

抓包看看websocket连接建立过程,请求头:

1
2
3
4
5
6
Connection: Upgrade
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: fL1y0DQDYrqGCwWPWIkDOw==
Sec-WebSocket-Protocol: vite-hmr
Sec-WebSocket-Version: 13
Upgrade: websocket

响应头

1
2
3
4
Connection: Upgrade
Sec-WebSocket-Accept: jWe7MVRbWtE+T/o2ZfoCecA3J7A=
Sec-WebSocket-Protocol: vite-hmr
Upgrade: websocket

image-20211125184013852

Nginx配置Upgrade

Nginx 从1.3.13开始支持Upgrade协议升级机制,看看官方给的示例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
This allows to proxy WebSockets by using configuration like this:

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

Connection upgrade is allowed as long as it was requested by a client
via the Upgrade request header.

Nginx Upgrade机制原理

为了跟进一步了解Nginx Upgrade机制的原理,翻了1.3.13版本的commit记录。

1
2
3
4
5
6
7
            if (u->headers_in.status_n == NGX_HTTP_SWITCHING_PROTOCOLS) {
                u->keepalive = 0;

                if (r->headers_in.upgrade) {
                    u->upgrade = 1;
                }
            }
1
2
3
4
    if (u->upgrade) {
        ngx_http_upstream_upgrade(r, u);
        return;
    }

根据上游服务器是否返回101状态码判断是否要upgreade,如果是则会执行ngx_http_upstream_upgrade函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
ngx_http_upstream_upgrade(ngx_http_request_t *r, ngx_http_upstream_t *u)
{
// ...

    u->read_event_handler = ngx_http_upstream_upgraded_read_upstream;
    u->write_event_handler = ngx_http_upstream_upgraded_write_upstream;
    r->read_event_handler = ngx_http_upstream_upgraded_read_downstream;
    r->write_event_handler = ngx_http_upstream_upgraded_write_downstream;

//  ...
}

ngx_http_upstream_upgrade函数主要是配置upgrade时的网络请写io回调,这几个回调最终都会调用ngx_http_upstream_process_upgraded函数,只不过是数据流向不同而已。

ngx_http_upstream_process_upgraded函数代码比较长就不贴了,主要作用就是纯socket数据读写,也就是TCP层的数据转发,而非正常http反向代理。

总结

根据RFC的定义,Nginx在通过上游响应的101状态码判断是否需要执行upgrade,需要就走tcp数据包转发,否则就走正常的http代理,这也解释了为什么websocket一旦建连后就不能在增加header。

参考

http://nginx.org/en/CHANGES

https://github.com/nginx/nginx/commit/08a73b4aadebd9405ac52ec1f6eef5ca1fe8c11a

https://blog.csdn.net/chenhanzhun/article/details/43524135