在 nginx 代理后面带有 node.js 的 HTTP2

HTTP2 with node.js behind nginx proxy

我在 nginx 代理后面有一个 node.js 服务器 运行。 node.js 是 运行 端口 3000 上的 HTTP 1.1(无 SSL)服务器。两者 运行 在同一台服务器上。

我最近将 nginx 设置为使用 HTTP2 和 SSL (h2)。似乎 HTTP2 确实已启用并且可以正常工作。

不过,我想知道代理连接 (nginx <--> node.js) 使用 HTTP 1.1 是否会影响性能。也就是说,我是否因为我的内部连接是 HTTP 1.1 而错过了 HTTP2 在速度方面的优势?

NGINX 不支持 HTTP/2 作为客户端。由于它们 运行 在同一台服务器上,并且没有延迟或带宽有限,我认为这两种方式都不会有很大的不同。我会确保您在 nginx 和 node.js.

之间使用 keepalive

https://www.nginx.com/blog/tuning-nginx/#keepalive

您通常不会损失性能,因为 nginx 通过向您的节点后端创建多个同时请求来匹配浏览器在 HTTP/2 上执行的请求多路复用。 (HTTP/2 的主要性能改进之一是允许浏览器通过同一连接同时执行多个请求,而在 HTTP 1.1 中每个连接只能同时请求一个。浏览器也限制连接数.)

一般来说,HTTP/2 最大的直接好处是 multiplexing 为浏览器连接提供的速度提升,而浏览器连接通常会受到高延迟(即往返速度慢)的阻碍。这些还减少了多个连接的需求(和费用),这是一种尝试在 HTTP/1.1.

中实现类似性能优势的解决方法

对于内部连接(例如在充当反向代理的网络服务器和后端应用程序服务器之间),延迟通常非常非常低,因此 HTTP/2 的速度优势可以忽略不计。此外,每个应用程序服务器通常已经是一个单独的连接,因此这里也没有任何好处。

因此,仅在边缘支持 HTTP/2,您将获得 大部分 的性能优势。这是一个相当常见的设置 - 类似于 HTTPS 通常在反向 proxy/load 平衡器上终止而不是一直通过的方式。

但是,始终支持 HTTP/2 有 潜在 好处。例如,它可以允许服务器从应用程序一路推送。由于 HTTP/2 和 header 压缩的二进制性质,最后一跳的数据包大小减小也可能带来好处。虽然,与延迟一样,带宽对于内部连接通常不是一个问题,因此其重要性值得商榷。最后一些人争辩说,反向代理连接 HTTP/2 连接到 HTTP/2 连接比连接 HTTP/1.1 连接做的工作更少,因为不需要将一种协议转换为另一种协议,尽管我怀疑这是否会引起注意,因为它们是单独的连接(除非它只是作为 TCP 通过代理)。所以,对我来说,端到端 HTTP/2 的主要原因是允许端到端服务器推送,但是 even that is probably better handled with HTTP Link Headers and 103-Early Hints due to the complications in managing push across multiple connections 并且我不知道任何支持此功能的 HTTP 代理服务器(足够少在后端支持 HTTP/2 没关系像这样链接 HTTP/2 连接)所以你需要一个第 4 层负载平衡器转发 TCP 打包程序而不是链接 HTTP 请求 - 这会带来其他复杂性。

目前,虽然服务器仍在添加支持并且服务器推送使用率很低(并且仍在试验以定义最佳实践),但我建议只在终点设置 HTTP/2。在撰写本文时,Nginx 也不支持 HTTP/2 用于 ProxyPass 连接(尽管 Apache 支持),并且具有 no plans to add this,并且他们提出了一个有趣的观点,即单个 HTTP/2连接可能会导致速度变慢(强调我的):

Is HTTP/2 proxy support planned for the near future?

Short answer:

No, there are no plans.

Long answer:

There is almost no sense to implement it, as the main HTTP/2 benefit is that it allows multiplexing many requests within a single connection, thus [almost] removing the limit on number of simalteneous requests - and there is no such limit when talking to your own backends. Moreover, things may even become worse when using HTTP/2 to backends, due to single TCP connection being used instead of multiple ones.

On the other hand, implementing HTTP/2 protocol and request multiplexing within a single connection in the upstream module will require major changes to the upstream module.

Due to the above, there are no plans to implement HTTP/2 support in the upstream module, at least in the foreseeable future. If you still think that talking to backends via HTTP/2 is something needed - feel free to provide patches.

最后,还应注意,虽然浏览器需要 HTTPS HTTP/2 (h2),但大多数服务器不需要,因此可以支持 HTTP (h2c) 上的最后一跳。因此,如果节点部分不存在端到端加密(通常不存在),则不需要端到端加密。但是,根据后端服务器相对于前端服务器的位置,如果流量将通过不安全的网络传输(例如,CDN 到互联网上的源服务器),即使是为此连接使用 HTTPS 也可能是应该考虑的事情。

编辑 2021 年 8 月

HTTP/1.1 是 text-based 而不是 binary does make it vulnerable to various request smuggling attacks. In Defcon 2021 PortSwigger demonstrated a number of real-life attacks,主要与将前端 HTTP/2 请求降级到后端 HTTP/1.1 时的问题有关要求。这些可能大部分可以通过一直说 HTTP/2 来避免,但是考虑到当前支持前端服务器和 CDN 与后端说 HTTP/2,并且后端支持 HTTP/2 似乎这需要很长时间才能普及,确保这些攻击不可利用的前端 HTTP/2 服务器似乎是更现实的解决方案。

NGINX 现在支持 proxy_pass 的 HTTP2/Push,这太棒了...

我在这里也从我的静态子域推送 favicon.ico、minified.css、minified.js、register.svg、purchase_litecoin.svg。我花了一些时间才意识到我可以从子域推送。

location / {
            http2_push_preload              on;
            add_header                      Link "<//static.yourdomain.io/css/minified.css>; as=style; rel=preload";
            add_header                      Link "<//static.yourdomain.io/js/minified.js>; as=script; rel=preload";
            add_header                      Link "<//static.yourdomain.io/favicon.ico>; as=image; rel=preload";
            add_header                      Link "<//static.yourdomain.io/images/register.svg>; as=image; rel=preload";
            add_header                      Link "<//static.yourdomain.io/images/purchase_litecoin.svg>; as=image; rel=preload";
            proxy_hide_header               X-Frame-Options;
            proxy_http_version              1.1;
            proxy_redirect                  off;
            proxy_set_header                Upgrade $http_upgrade;
            proxy_set_header                Connection "upgrade";
            proxy_set_header                X-Real-IP $remote_addr;
            proxy_set_header                Host $http_host;
            proxy_set_header                X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header                X-Forwarded-Proto $scheme;
            proxy_pass                      http://app_service;
        }

以防有人在不方便使您的服务与 HTTP2 兼容的情况下寻找解决方案。以下是可用于将 HTTP1 服务转换为 HTTP2 服务的基本 NGINX 配置。

server {
  listen [::]:443 ssl http2;
  listen 443 ssl http2;

  server_name localhost;
  ssl on;
  ssl_certificate /Users/xxx/ssl/myssl.crt;
  ssl_certificate_key /Users/xxx/ssl/myssl.key;

  location / {
    proxy_pass http://localhost:3001;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
  }
}