七行代码解决 Nginx 中 Http 和 WebSocket 的反向代理

Nginx实现Http和WebSocket的反向代理

通常时候代理 websocket 我们会这样做

server {
    ...

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

‍有鱼友可能要问了那么为什么要设定header中的ConnectionUpgrade呢?

原因是发起 websocket 请求的时候,请求标头会带上 ConnectionUpgrade

Connection: Upgrade
Upgrade: websocket

那么‍这俩参数在这段连接中起到什么样的作用呢?

我们来看一段 nginx 官网文档对 ws 代理的描述

原文:

To turn a connection between a client and server from HTTP/1.1 into WebSocket, the protocol switch mechanism available in HTTP/1.1 is used.

There is one subtlety however: since the “Upgrade” is a hop-by-hop header, it is not passed from a client to proxied server. With forward proxying, clients may use the CONNECT method to circumvent this issue. This does not work with reverse proxying however, since clients are not aware of any proxy servers, and special processing on a proxy server is required.

Since version 1.3.13, nginx implements special mode of operation that allows setting up a tunnel between a client and proxied server if the proxied server returned a response with the code 101 (Switching Protocols), and the client asked for a protocol switch via the “Upgrade” header in a request.

翻译:

要将客户端和服务器之间的连接从 HTTP/1.1 转换为 WebSocket, 《议定书》 使用 HTTP/1.1 中可用的切换机制。

但是,有一个微妙之处:由于“Upgrade”是 hop-by-hop 标头,因此它不会从客户端传递到代理服务器。 使用转发代理,客户端可以使用该方法来规避此问题。 但是,这不适用于反向代理, 由于客户端不知道任何代理服务器, 并且需要在代理服务器上进行特殊处理。CONNECT

从版本 1.3.13 开始, nginx 实现特殊操作模式 允许在客户端和代理之间设置隧道 服务器(如果代理服务器返回了带有代码的响应) 101(交换协议), 客户端通过"Upgrade"要求协议切换 请求中的标头。

‍我们可以看到,当发起WebSocket请求的时候,需要对HTTP协议进行升级。需要将客户端和服务器之间的连接从 HTTP/1.1 转换为 WebSocket。

那么为什么需要在nginx的配置文件中设置(传递)这两个参数呢?

上文中提到了关于 hop-by-hop,以下是hop-by-hop的描述:

End-to-end and Hop-by-hop Headers

端到端和逐跳报头

为了定义缓存和非缓存代理的行为,我们将 HTTP 报头分为两类:

  • End-to-end 消息头,发送给请求或响应的最终接收者。响应中的端到端头必须作为缓存项的一部分存储,并且必须在缓存项形成的任何响应中传输。
  • Hop-by-hop 报头,仅对单个传输级连接有意义,不由缓存存储或由代理转发。

下面的 HTTP/1.1 报头是逐跳报头:

  • Connection
  • Keep-Alive
  • Proxy-Authenticate
  • Proxy-Authorization
  • TE
  • Trailers
  • Transfer-Encoding
  • Upgrade

HTTP/1.1 定义的所有其他报头都是端到端报头。

其他逐跳报头必须在 Connection 报头中列出,(章节 14.10)将被引入 HTTP/1.1(或更高版本)。

ok, 从协议书中得出,hop-by-hop 报头仅对单个传输级连接有意义,不由缓存储存或代理转发,那么我们必须在网关层就对其进行处理。包括 ConnectionUpgrade 在内的报头不会从客户端传递到代理服务器,因此为了让代理服务器了解客户端打算将协议升级到 WebSocket,这些报头需要在网关层通过。

说人话就是,ConnectionUpgrade这俩标头在经过Nginx代理后,不会被后面的服务感知到,无法进行协议的升级,所以要在配置文件中进行参数传递。

但是当 websocket 请求很多的时候,会让配置显得很复杂,那么可以用 map 来根据客户端请求表头中的 Connection 字段是否存在,来判断是否需要协议升级和转发给代理服务器。

http {
    map $http_upgrade $connection_upgrade {
        default     keep-alive;
        'websocket' Upgrade;
    }

    server {
        ...

        location /websocket{
            proxy_pass http://myserver/websocket;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
        }
    }
}

使用以上配置时,当Http 标头中的Upgrade字段为 websocket 的时候,这时候会将变量$connection_upgrade设置为Upgrade,从而完成协议的自动。如果是普通的http请求,则Connection为keep-alive,以方便下次请求。

经 @test12138 提醒,修改文档中的错误代码