这是超文本传输协议版本 2 的 C 实现。
HTTP/2 的框架层是作为可重用的 C 库实现的。最重要的是,我们实现了 HTTP/2 客户端、服务器和代理。我们还开发了 HTTP/2 的负载测试和基准测试工具。
HPACK 编码器和解码器可作为公共 API 使用。
nghttp2 最初是基于 RFC 7540 HTTP/2 和 RFC 7541 HPACK - HTTP/2 标头压缩开发的。现在我们正在更新代码以实现 RFC 9113。
nghttp2 代码库是从 spdylay (https://github.com/tatsuhiro-t/spdylay) 项目中分叉出来的。
以下端点可用于尝试我们的 nghttp2 实现。
https://nghttp2.org/(TLS + ALPN 和 HTTP/3)
此端点通过 ALPN 支持h2
、 h2-16
、 h2-14
和http/1.1
,并需要 TLSv1.2 进行 HTTP/2 连接。
它还支持 HTTP/3。
http://nghttp2.org/(HTTP 升级和 HTTP/2 直接)
h2c
和http/1.1
。
构建 libnghttp2 库需要以下包:
要构建文档,您需要安装:
如果您只需要 libnghttp2(C 库),那么上述包就足够了。使用--enable-lib-only
确保仅构建 libnghttp2。这可以避免与构建捆绑应用程序相关的潜在构建错误。
要在src
目录中构建并运行应用程序( nghttp
、 nghttpd
、 nghttpx
和h2load
),需要以下包:
要在nghttp
中启用-a
选项(从下载的资源中获取链接资源),需要以下包:
要在 nghttpx 中启用 systemd 支持,需要以下软件包:
HPACK 工具需要以下软件包:
要在示例目录下构建源代码,需要 libevent:
为了减少长时间运行的服务器程序( nghttpd
和nghttpx
)中的堆碎片,建议使用 jemalloc :
杰马洛克
笔记
由于 musl 限制,Alpine Linux 目前不支持 malloc 替换。请参阅问题#762 中的详细信息。
对于 BoringSSL 或 aws-lc 构建,要在应用程序中启用 RFC 8879 TLS 证书压缩,需要以下库:
要启用 nghttpx 的 mruby 支持,需要 mruby。我们需要显式打开 C++ ABI 来构建 mruby,并且可能还需要其他 mrgems,mruby 由第三方/mruby 目录下的 git 子模块管理。目前,mruby 对 nghttpx 的支持默认处于禁用状态。要启用 mruby 支持,请使用--with-mruby
配置选项。请注意,在撰写本文时,Debian/Ubuntu 中的 libmruby-dev 和 mruby 软件包不可用于 nghttp2,因为它们不启用 C++ ABI。要构建 mruby,需要以下软件包:
nghttpx 支持 OpenSSL 的 neverbleed 权限分离引擎。简而言之,当像 Heartbleed 这样的严重漏洞被利用时,它可以最大限度地降低私钥泄露的风险。默认情况下,neverbleed 处于禁用状态。要启用它,请使用--with-neverbleed
配置选项。
要启用 h2load 和 nghttpx 的实验性 HTTP/3 支持,需要以下库:
使用--enable-http3
配置选项为 h2load 和 nghttpx 启用 HTTP/3 功能。
为了构建可选的 eBPF 程序以将传入的 QUIC UDP 数据报定向到 nghttpx 的正确套接字,需要以下库:
使用--with-libbpf
配置选项构建 eBPF 程序。构建 libbpf 需要 libelf-dev。
对于 Ubuntu 20.04,您可以从源代码构建 libbpf。 nghttpx 需要 eBPF 程序来重新加载其配置并热交换其可执行文件。
编译 libnghttp2 C 源代码需要 C99 编译器。已知 gcc 4.8 就足够了。为了编译 C++ 源代码,需要 C++20 兼容的编译器。至少 g++ >= 12 和 clang++ >= 15 已知可以工作。
笔记
要在 nghttpx 中启用 mruby 支持,并使用--with-mruby
配置选项。
笔记
Mac OS X 用户可能需要--disable-threads
配置选项来禁用 nghttpd、nghttpx 和 h2load 中的多线程,以防止它们崩溃。欢迎提供补丁以使多线程在 Mac OS X 平台上工作。
笔记
要编译关联的应用程序(nghttp、nghttpd、nghttpx 和 h2load),您必须使用--enable-app
配置选项并确保满足上述指定的要求。通常,配置脚本会检查构建这些应用程序所需的依赖项,并自动启用--enable-app
,因此您不必显式使用它。但如果您发现应用程序未构建,那么使用--enable-app
可能会找到原因,例如缺少依赖项。
笔记
为了检测第三方库,使用了pkg-config(但是对于某些库(例如libev)我们不使用pkg-config)。默认情况下,pkg-config 在标准位置(例如/usr/lib/pkgconfig)搜索*.pc
文件。如果需要在自定义位置使用*.pc
文件,请指定PKG_CONFIG_PATH
环境变量的路径,并将其传递给配置脚本,如下所示:
$ ./configure PKG_CONFIG_PATH=/path/to/pkgconfig
对于 pkg-config 托管库,定义了*_CFLAG
和*_LIBS
环境变量(例如OPENSSL_CFLAGS
、 OPENSSL_LIBS
)。为这些变量指定非空字符串会完全覆盖 pkg-config。换句话说,如果指定了它们,pkg-config 不会用于检测,用户负责为这些变量指定正确的值。有关这些变量的完整列表,请运行./configure -h
。
如果您使用的是 Ubuntu 22.04 LTS,请运行以下命令来安装所需的软件包:
sudo apt-get install g++ clang make binutils autoconf automake
autotools-dev libtool pkg-config
zlib1g-dev libssl-dev libxml2-dev libev-dev
libevent-dev libjansson-dev
libc-ares-dev libjemalloc-dev libsystemd-dev
ruby-dev 野牛 libelf-dev
nghttp2 项目定期发布 tar 档案,其中包括 nghttp2 源代码和生成的构建文件。它们可以从发布页面下载。
从 git 构建 nghttp2 需要 autotools 开发包。从 tar 档案构建不需要它们,因此更容易。通常的构建步骤如下:
$ tar xf nghttp2-XYZtar.bz2
$ cd nghttp2-XYZ
$ ./配置
$ 制作
从 git 构建很容易,但请确保至少使用 autoconf 2.68:
$ git 子模块更新 --init
$ autoreconf -i
$汽车制造商
$ 自动配置
$ ./配置
$ 制作
构建本机 Windows nghttp2 dll 的最简单方法是使用 cmake。 Visual C++ Build Tools 的免费版本运行良好。
cmake
。cmake --build
来构建库。请注意,上述步骤很可能仅生成 nghttp2 库。不编译任何捆绑应用程序。
在 Mingw 环境下,只能编译库,即libnghttp2-X.dll
和libnghttp2.a
。
如果要编译应用程序( h2load
、 nghttp
、 nghttpx
、 nghttpd
),则需要使用 Cygwin 环境。
在Cygwin环境下,要编译应用程序需要先编译并安装libev。
其次,您需要取消定义宏__STRICT_ANSI__
,否则,函数fdopen
、 fileno
和strptime
将不可用。
示例命令如下:
$ 导出 CFLAGS="-U__STRICT_ANSI__ -I$libev_PREFIX/include -L$libev_PREFIX/lib"
$ 导出 CXXFLAGS=$CFLAGS
$ ./配置
$ 制作
如果你想编译examples/
下的应用程序,你需要从 libev 的安装中删除或重命名event.h
,因为它与 libevent 的安装冲突。
使用make install
安装 nghttp2 工具套件后,可能会遇到类似的错误:
nghttpx:加载共享库时出错:libnghttp2.so.14:无法打开共享对象文件:没有这样的文件或目录
这意味着该工具无法找到libnghttp2.so
共享库。
要更新共享库缓存,请运行sudo ldconfig
。
笔记
文档仍然不完整。
要构建文档,请运行:
$ 制作 html
文档将在doc/manual/html/
下生成。
生成的文档不会使用make install
进行安装。
在线文档位于 https://nghttp2.org/documentation/
要构建启用 HTTP/3 功能的 h2load 和 nghttpx,请使用--enable-http3
运行配置脚本。
为了让 nghttpx 重新加载配置并交换其可执行文件,同时优雅地终止旧的工作进程,需要 eBPF。使用--enable-http3 --with-libbpf
运行配置脚本来构建 eBPF 程序。 QUIC 密钥材料必须使用--frontend-quic-secret-file
设置,以便在重新加载期间保持现有连接处于活动状态。
下面是构建支持 HTTP/3 的 h2load 和 nghttpx 的详细步骤。
构建 aws-lc:
$ git clone --深度 1 -b v1.39.0 https://github.com/aws/aws-lc
$ cd aws-lc
$ cmake -B build -DDISABLE_GO=ON --install-prefix=$PWD/opt
$ make -j$(nproc) -C 构建
$ cmake --安装构建
$ 光盘 ..
构建 nghttp3:
$ git clone --深度 1 -b v1.6.0 https://github.com/ngtcp2/nghttp3
$ cd nghttp3
$ git 子模块更新 --init --深度 1
$ autoreconf -i
$ ./configure --prefix=$PWD/build --enable-lib-only
$ make -j$(nproc)
$ 进行安装
$ 光盘 ..
构建 ngtcp2:
$ git clone --深度1 -b v1.9.1 https://github.com/ngtcp2/ngtcp2
$ cd ngtcp2
$ git 子模块更新 --init --深度 1
$ autoreconf -i
$ ./configure --prefix=$PWD/build --enable-lib-only --with-boringssl
BORINGSSL_CFLAGS="-I$PWD/../aws-lc/opt/include"
BORINGSSL_LIBS="-L$PWD/../aws-lc/opt/lib -lssl -lcrypto"
$ make -j$(nproc)
$ 进行安装
$ 光盘 ..
如果您的 Linux 发行版没有 libbpf-dev >= 0.7.0,请从源代码构建:
$ git clone --深度 1 -b v1.4.6 https://github.com/libbpf/libbpf
$ cd libbpf
$ PREFIX=$PWD/build make -C src install
$ 光盘 ..
构建 nghttp2:
$ git 克隆 https://github.com/nghttp2/nghttp2
$ cd nghttp2
$ git 子模块更新 --init
$ autoreconf -i
$ ./configure --with-mruby --enable-http3 --with-libbpf
CC=clang-15 CXX=clang++-15
PKG_CONFIG_PATH="$PWD/../aws-lc/opt/lib/pkgconfig:$PWD/../nghttp3/build/lib/pkgconfig:$PWD/../ngtcp2/build/lib/pkgconfig:$PWD/ ../libbpf/build/lib64/pkgconfig"
LDFLAGS="$LDFLAGS -Wl,-rpath,$PWD/../aws-lc/opt/lib -Wl,-rpath,$PWD/../libbpf/build/lib64"
$ make -j$(nproc)
eBPF 程序reuseport_kern.o
应在bpf 目录下找到。将--quic-bpf-program-file=bpf/reuseport_kern.o
选项传递给 nghttpx 来加载它。另请参阅 nghttpx - HTTP/2 代理 - HOW-TO 中的 HTTP/3 部分。
单元测试只需运行make check
即可完成。
我们对 nghttpx 代理服务器进行了集成测试。测试是用 Go 编程语言编写的,并使用其测试框架。我们依赖以下库:
Go 模块会自动下载这些依赖项。
要运行测试,请在integration-tests
目录下运行以下命令:
$ 做到了
在测试中,我们使用端口 3009 来运行测试主题服务器。
nghttp2 v1.0.0 引入了一些向后不兼容的更改。在本节中,我们将描述这些更改以及如何迁移到 v1.0.0。
h2
和h2c
之前我们宣布了h2-14
和h2c-14
。 v1.0.0 实现了最终协议版本,我们将 ALPN ID 更改为h2
和h2c
。宏NGHTTP2_PROTO_VERSION_ID
、 NGHTTP2_PROTO_VERSION_ID_LEN
、 NGHTTP2_CLEARTEXT_PROTO_VERSION_ID
和NGHTTP2_CLEARTEXT_PROTO_VERSION_ID_LEN
已更新以反映此更改。
基本上,现有的应用程序不需要做任何事情,只需重新编译就足以进行此更改。
我们使用“客户端连接前言”来表示客户端连接前言的前 24 个字节。这在技术上是不正确的,因为客户端连接前言由 24 字节客户端魔术字节字符串后跟 SETTINGS 帧组成。为了澄清起见,我们将此 24 字节字符串和更新的 API 称为“客户端魔法”。
NGHTTP2_CLIENT_CONNECTION_PREFACE
已替换为NGHTTP2_CLIENT_MAGIC
。NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN
已替换为NGHTTP2_CLIENT_MAGIC_LEN
。NGHTTP2_BAD_PREFACE
已重命名为NGHTTP2_BAD_CLIENT_MAGIC
已弃用的NGHTTP2_CLIENT_CONNECTION_HEADER
和NGHTTP2_CLIENT_CONNECTION_HEADER_LEN
已被删除。
如果应用程序使用这些宏,只需用新宏替换旧宏即可。从 v1.0.0 开始,客户端魔法由库发送(请参阅下一小节),因此客户端应用程序可能会删除这些宏的使用。
以前nghttp2库没有发送客户端魔术,这是客户端连接序言的第一个24字节字节字符串,客户端应用程序必须自己发送它。从 v1.0.0 开始,客户端魔法由库通过第一次调用nghttp2_session_send()
或nghttp2_session_mem_send2()
发送。
发送客户端魔法的客户端应用程序必须删除相关代码。
Alt-Svc 规范尚未最终确定。为了使我们的 API 稳定,我们决定从 nghttp2 中删除所有与 Alt-Svc 相关的 API。
NGHTTP2_EXT_ALTSVC
已删除。nghttp2_ext_altsvc
已被删除。我们已经在 v0.7 系列中删除了 Alt-Svc 的功能,它们本质上是空的。使用这些宏和结构的应用程序,删除这些行。
以前nghttp2_on_invalid_frame_recv_cb_called
将nghttp2_error_code
中定义的error_code
作为参数。但它们不够详细,无法调试。因此,我们决定使用更详细的nghttp2_error
值。
使用此回调的应用程序应更新回调签名。如果它将error_code
视为 HTTP/2 错误代码,请更新代码,以便将其视为nghttp2_error
。
以前 nghttp2 不处理客户端魔法(24 字节字节字符串)。为了让它处理这个问题,我们必须使用nghttp2_option_set_recv_client_preface()
。从 v1.0.0 开始,nghttp2 默认处理客户端魔法,并且nghttp2_option_set_recv_client_preface()
被删除。
某些应用程序可能希望禁用此行为,因此我们添加了nghttp2_option_set_no_recv_client_magic()
来实现此目的。
使用nghttp2_option_set_recv_client_preface()
且值非零的应用程序,只需将其删除即可。
使用零值或不使用nghttp2_option_set_recv_client_preface()
的应用程序必须使用非零值的nghttp2_option_set_no_recv_client_magic()
。
src
目录包含 HTTP/2 客户端、服务器和代理程序。
nghttp
是一个 HTTP/2 客户端。它可以通过先验知识、HTTP 升级和 ALPN TLS 扩展连接到 HTTP/2 服务器。
它具有用于帧信息的详细输出模式。以下是nghttp
客户端的示例输出:
$ nghttp -nv https://nghttp2.org
[0.190]已连接
协商协议:h2
[0.212]接收设置帧<长度= 12,标志= 0x00,stream_id = 0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[设置_初始_窗口_大小(0x04):65535]
[0.212]发送设置帧<长度= 12,标志= 0x00,stream_id = 0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[设置_初始_窗口_大小(0x04):65535]
[0.212]发送设置帧<长度= 0,标志= 0x01,stream_id = 0>
;确认
(niv=0)
[0.212]发送优先级帧
(dep_stream_id=0,权重=201,独占=0)
[0.212]发送优先级帧
(dep_stream_id=0,权重=101,独占=0)
[0.212]发送优先级帧
(dep_stream_id=0,权重=1,独占=0)
[0.212]发送优先级帧
(dep_stream_id=7,权重=1,独占=0)
[0.212]发送优先级帧
(dep_stream_id=3,权重=1,独占=0)
[ 0.212] 发送标头帧
; END_STREAM | END_HEADERS |优先事项
(padlen = 0,dep_stream_id = 11,权重= 16,独占= 0)
;打开新流
:方法:获取
:小路: /
:方案:https
:权威:nghttp2.org
接受: */*
接受编码:gzip、deflate
用户代理:nghttp2/1.0.1-DEV
[0.221]接收设置帧<长度= 0,标志= 0x01,stream_id = 0>
;确认
(niv=0)
[0.221]recv(stream_id = 13):方法:GET
[0.221]recv(stream_id = 13):方案:https
[0.221]recv(stream_id=13):路径:/stylesheets/screen.css
[0.221]recv(stream_id = 13):权威:nghttp2.org
[0.221]recv(stream_id = 13)接受编码:gzip,deflate
[0.222]recv(stream_id=13)用户代理:nghttp2/1.0.1-DEV
[0.222]接收PUSH_PROMISE帧<长度= 50,标志= 0x04,stream_id = 13>
; END_HEADERS
(padlen = 0,promise_stream_id = 2)
[0.222]recv(stream_id = 13):状态:200
[0.222]recv(stream_id = 13)日期:2015年5月21日星期四16:38:14 GMT
[0.222]recv(stream_id=13)内容类型:text/html
[0.222]recv(stream_id=13)最后修改时间:2015 年 5 月 15 日星期五 15:38:06 GMT
[0.222]recv(stream_id = 13)etag:W /“555612de-19f6”
[0.222]recv(stream_id=13)链接:; rel=预载; as=样式表
[0.222]recv(stream_id = 13)内容编码:gzip
[0.222]recv(stream_id=13)服务器:nghttpx nghttp2/1.0.1-DEV
[0.222]recv(stream_id = 13)通过:1.1 nghttpx
[0.222]recv(stream_id = 13)严格传输安全:最大年龄= 31536000
[0.222]recv标头帧<长度= 166,标志= 0x04,stream_id = 13>
; END_HEADERS
(padlen=0)
;第一个响应头
[0.222]接收数据帧<长度= 2601,标志= 0x01,stream_id = 13>
; END_STREAM
[0.222]recv(stream_id = 2):状态:200
[0.222]recv(stream_id = 2)日期:2015年5月21日星期四16:38:14 GMT
[0.222]recv(stream_id=2)内容类型:text/css
[0.222]recv(stream_id=2)最后修改时间:2015 年 5 月 15 日星期五 15:38:06 GMT
[0.222]recv(stream_id = 2)etag:W /“555612de-9845”
[0.222]recv(stream_id = 2)内容编码:gzip
[0.222]recv(stream_id=2)服务器:nghttpx nghttp2/1.0.1-DEV
[0.222]recv(stream_id = 2)通过:1.1 nghttpx
[0.222]recv(stream_id = 2)严格传输安全:最大年龄= 31536000
[0.222]recv标头帧<长度= 32,标志= 0x04,stream_id = 2>
; END_HEADERS
(padlen=0)
;首先推送响应头
[0.228]接收数据帧<长度= 8715,标志= 0x01,stream_id = 2>
; END_STREAM
[ 0.228] 发送 GOAWAY 帧
(last_stream_id = 2,error_code = NO_ERROR(0x00),opaque_data(0)= [])
HTTP 升级的执行方式如下:
$ nghttp -nvu http://nghttp2.org
[0.011]已连接
[0.011]HTTP升级请求
获取/HTTP/1.1
主办方:nghttp2.org
连接:升级、HTTP2 设置
升级:h2c
HTTP2 设置:AAMAAAABkAAQAAP__
接受: */*
用户代理:nghttp2/1.0.1-DEV
[0.018]HTTP升级响应
HTTP/1.1 101 切换协议
连接:升级
升级:h2c
[0.018]HTTP升级成功
[0.018]接收设置帧<长度= 12,标志= 0x00,stream_id = 0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[设置_初始_窗口_大小(0x04):65535]
[ 0.018] 发送 SETTINGS 帧
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[设置_初始_窗口_大小(0x04):65535]
[ 0.018] 发送 SETTINGS 帧
;确认
(niv=0)
[ 0.018] 发送优先级帧
(dep_stream_id=0,权重=201,独占=0)
[ 0.018] 发送优先级帧
(dep_stream_id=0,权重=101,独占=0)
[ 0.018] 发送优先级帧
(dep_stream_id=0,权重=1,独占=0)
[ 0.018] 发送优先级帧
(dep_stream_id=7,权重=1,独占=0)
[ 0.018] 发送优先级帧
(dep_stream_id=3,权重=1,独占=0)
[ 0.018] 发送优先级帧
(dep_stream_id=11,权重=16,独占=0)
[0.019]recv(stream_id = 1):方法:GET
[0.019]recv(stream_id = 1):方案:http
[0.019]recv(stream_id=1):路径:/stylesheets/screen.css
[0.019]recv(stream_id = 1)主机:nghttp2.org
[0.019]recv(stream_id=1)用户代理:nghttp2/1.0.1-DEV
[0.019]接收PUSH_PROMISE帧<长度= 49,标志= 0x04,stream_id = 1>
; END_HEADERS
(padlen = 0,promise_stream_id = 2)
[0.019]recv(stream_id = 1):状态:200
[0.019]recv(stream_id = 1)日期:2015年5月21日星期四16:39:16 GMT
[0.019]recv(stream_id=1)内容类型:text/html
[0.019]recv(stream_id = 1)内容长度:6646
[0.019]recv(stream_id=1)最后修改时间:2015 年 5 月 15 日星期五 15:38:06 GMT
[0.019]recv(stream_id=1)etag:“555612de-19f6”
[0.019]recv(stream_id=1)链接:; rel=预载; as=样式表
[0.019]recv(stream_id = 1)接受范围:字节
[0.019]recv(stream_id=1)服务器:nghttpx nghttp2/1.0.1-DEV
[0.019]recv(stream_id = 1)通过:1.1 nghttpx
[0.019]recv标头帧<长度= 157,标志= 0x04,stream_id = 1>
; END_HEADERS
(padlen=0)
;第一个响应头
[0.019]接收数据帧<长度= 6646,标志= 0x01,stream_id = 1>
; END_STREAM
[0.019]recv(stream_id = 2):状态:200
[0.019]recv(stream_id = 2)日期:2015年5月21日星期四16:39:16 GMT
[0.019]recv(stream_id=2)内容类型:文本/css
[0.019]recv(stream_id = 2)内容长度:38981
[0.019]recv(stream_id=2)最后修改时间:2015 年 5 月 15 日星期五 15:38:06 GMT
[0.019]recv(stream_id=2)etag:“555612de-9845”
[0.019]recv(stream_id = 2)接受范围:字节
[0.019]recv(stream_id=2)服务器:nghttpx nghttp2/1.0.1-DEV
[0.019]recv(stream_id = 2)通过:1.1 nghttpx
[0.019]recv标头帧<长度= 36,标志= 0x04,stream_id = 2>
; END_HEADERS
(padlen=0)
;首先推送响应头
[0.026]接收数据帧<长度= 16384,标志= 0x00,stream_id = 2>
[0.027]接收数据帧<长度= 7952,标志= 0x00,stream_id = 2>
[ 0.027] 发送 WINDOW_UPDATE 帧
(窗口大小增量=33343)
[ 0.032] 发送 WINDOW_UPDATE 帧
(窗口大小增量=33707)
[0.032]接收数据帧<长度= 14645,标志= 0x01,stream_id = 2>
; END_STREAM
[0.032]接收设置帧<长度= 0,标志= 0x01,stream_id = 0>
;确认
(niv=0)
[ 0.032] 发送 GOAWAY 帧
(last_stream_id = 2,error_code = NO_ERROR(0x00),opaque_data(0)= [])
使用-s
选项, nghttp
打印出一些请求的计时信息,按完成时间排序:
$ nghttp -nas https://nghttp2.org/
***** 统计数据 *****
请求时间:
responseEnd:收到响应的最后一个字节的时间
相对于连接结束
requestStart:发送请求的第一个字节之前的时间
相对于 connectEnd。如果显示“*”,则为
由服务器推送。
流程:响应结束-请求开始
代码:HTTP状态代码
大小:作为响应正文接收的字节数,不包含
通货膨胀。
URI:请求URI
请参阅http://www.w3.org/TR/resource-timing/#processing-model
按“完整”排序
id responseEnd requestStart 处理代码 大小 请求路径
13 +37.19ms +280us 36.91ms 200 2K /
2 +72.65ms * +36.38ms 36.26ms 200 8K /stylesheets/screen.css
17 +77.43ms +38.67ms 38.75ms 200 3K /javascripts/octopress.js
15 +78.12ms +38.66ms 39.46ms 200 3K /javascripts/modernizr-2.0.js
使用-r
选项, nghttp
将更详细的计时数据以 HAR 格式写入给定文件。
nghttpd
是一个多线程静态 Web 服务器。
默认情况下,它使用 SSL/TLS 连接。使用--no-tls
选项禁用它。
nghttpd
仅接受通过 ALPN 的 HTTP/2 连接或直接 HTTP/2 连接。不支持 HTTP 升级。
-p
选项允许用户配置服务器推送。
就像nghttp
一样,它有一个用于帧信息的详细输出模式。以下是nghttpd
的示例输出:
$ nghttpd --no-tls -v 8080
IPv4:监听 0.0.0.0:8080
IPv6: 听 :::8080
[id=1] [1.521] 发送 SETTINGS 帧
(niv=1)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[id=1] [1.521] 接收设置帧<长度=12,标志=0x00,stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[设置_初始_窗口_大小(0x04):65535]
[id=1] [1.521] 接收设置帧<长度=0,标志=0x01,stream_id=0>
;确认
(niv=0)
[id=1] [1.521] 接收优先级帧
(dep_stream_id=0,权重=201,独占=0)
[id=1] [1.521] 接收优先级帧
(dep_stream_id=0,权重=101,独占=0)
[id=1] [1.521] 接收优先级帧
(dep_stream_id=0,权重=1,独占=0)
[id=1] [1.521] 接收优先级帧
(dep_stream_id=7,权重=1,独占=0)
[id=1] [1.521] 接收优先级帧
(dep_stream_id=3,权重=1,独占=0)
[id=1][1.521]recv(stream_id=13):方法:GET
[id=1][1.521]recv(stream_id=13):路径:/
[id=1][1.521]recv(stream_id=13):方案:http
[id=1][1.521]recv(stream_id=13):权限:localhost:8080
[id=1][1.521]recv(stream_id=13)接受:*/*
[id=1] [1.521] recv (stream_id=13) 接受编码:gzip、deflate
[id=1] [1.521] recv (stream_id=13) 用户代理:nghttp2/1.0.0-DEV
[id=1] [1.521] recv HEADERS 帧
; END_STREAM | END_HEADERS |优先事项
(padlen = 0,dep_stream_id = 11,权重= 16,独占= 0)
;打开新流
[id=1] [1.521] 发送 SETTINGS 帧
;确认
(niv=0)
[id=1] [1.521] 发送 HEADERS 帧
; END_HEADERS
(padlen=0)
;第一个响应头
:状态:200
服务器:nghttpd nghttp2/1.0.0-DEV
内容长度:10
缓存控制:最大年龄=3600
日期: 2015 年 5 月 15 日星期五 14:49:04 GMT
最后修改时间:2014 年 9 月 30 日星期二 12:40:52 GMT
[id=1] [1.522] 发送数据帧
; END_STREAM
[id=1] [1.522]stream_id=13 已关闭
[id=1] [1.522] recv GOAWAY 帧
(last_stream_id = 0,error_code = NO_ERROR(0x00),opaque_data(0)= [])
[id=1] [1.522] 已关闭
nghttpx
是 HTTP/3、HTTP/2 和 HTTP/1.1 的多线程反向代理,为 http://nghttp2.org 提供支持并支持 HTTP/2 服务器推送。
我们重新设计了nghttpx
命令行界面,因此,1.8.0 或更早版本存在一些不兼容的情况。这对于扩展其功能并确保未来版本中的进一步功能增强是必要的。请阅读从 nghttpx v1.8.0 或更早版本迁移以了解如何从早期版本迁移。
nghttpx
在 TLS 中实现了重要的面向性能的功能,例如会话 ID、会话票证(具有自动密钥轮换)、OCSP 装订、动态记录大小、ALPN、前向保密和 HTTP/2。 nghttpx
还提供通过 memcached 在多个nghttpx
实例之间共享会话缓存和票证密钥的功能。
nghttpx
有 2 种运行模式:
模式选项 | 前端 | 后端 | 笔记 |
---|---|---|---|
默认模式 | HTTP/3、HTTP/2、HTTP/1.1 | HTTP/1.1、HTTP/2 | 反向代理 |
--http2-proxy | HTTP/3、HTTP/2、HTTP/1.1 | HTTP/1.1、HTTP/2 | 正向代理 |
目前有趣的模式是默认模式。它的工作方式类似于反向代理,侦听 HTTP/3、HTTP/2 和 HTTP/1.1,并且可以部署为现有 Web 服务器的 SSL/TLS 终结器。
在所有模式下,前端连接默认通过 SSL/TLS 加密。要禁用加密,请在--frontend
选项中使用no-tls
关键字。如果禁用加密,传入的 HTTP/1.1 连接可以通过 HTTP Upgrade 升级到 HTTP/2。在另一块硬盘上,后端连接默认不加密。要加密后端连接,请在--backend
选项中使用tls
关键字。
nghttpx
支持配置文件。请参阅--conf
选项和示例配置文件nghttpx.conf.sample
。
在默认模式下, nghttpx
作为后端服务器的反向代理:
客户端 <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1, HTTP/2) --> Web 服务器
[反向代理]
使用--http2-proxy
选项,它充当转发代理,即所谓的安全 HTTP/2 代理:
客户端 <-- (HTTP/3、HTTP/2、HTTP/1.1) --> nghttpx <-- (HTTP/1.1) --> 代理
[安全代理](例如,Squid、ATS)
上面示例中的Client
需要配置为使用nghttpx
作为安全代理。
在撰写本文时,Chrome 和 Firefox 都支持安全 HTTP/2 代理。将 Chrome 配置为使用安全代理的一种方法是创建一个 proxy.pac 脚本,如下所示:
function FindProxyForURL ( url , host ) {
return "HTTPS SERVERADDR:PORT" ;
}
SERVERADDR
和PORT
是运行 nghttpx 的机器的主机名/地址和端口。请注意,Chrome 需要有效的安全代理证书。
然后使用以下参数运行 Chrome:
$ google-chrome --proxy-pac-url=file:///path/to/proxy.pac --use-npn
后端 HTTP/2 连接可以通过 HTTP 代理进行隧道传输。代理是使用--backend-http-proxy-uri
指定的。下图说明了 nghttpx 如何通过 HTTP 代理与外部 HTTP/2 代理进行通信:
客户端 <-- (HTTP/3、HTTP/2、HTTP/1.1) --> nghttpx <-- (HTTP/2) --
--======================---> HTTP/2 代理
(HTTP 代理隧道)(例如 nghttpx -s)
h2load
程序是 HTTP/3、HTTP/2 和 HTTP/1.1 的基准测试工具。 h2load
的 UI 深受weighttp
(https://github.com/lighttpd/weighttp) 的启发。典型用法如下:
$ h2load -n100000 -c100 -m100 https://localhost:8443/
开始基准测试...
生成线程 #0:100 个并发客户端,100000 个总请求
协议:TLSv1.2
密码:ECDHE-RSA-AES128-GCM-SHA256
服务器临时密钥:ECDH P-256 256 位
进度:完成10%
进度:完成20%
进度:完成30%
进度:已完成 40%
进度:已完成 50%
进度:完成 60%
进度:完成70%
进度:完成80%
进度:完成90%
进度:100%完成
完成时间为 771.26ms,129658 req/s,4.71MB/s
请求:总共 100000 个、已启动 100000 个、已完成 100000 个、成功 100000 个、失败 0 个、出错 0 个
状态代码:100000 2xx、0 3xx、0 4xx、0 5xx
流量:总计 3812300 字节,标头 1009900 字节,数据 1000000 字节
最小值 最大值 平均标准差 +/- 标准差
请求时间:25.12ms 124.55ms 51.07ms 15.36ms 84.87%
连接时间:208.94ms 254.67ms 241.38ms 7.95ms 63.00%
到第一个字节的时间:209.11ms 254.80ms 241.51ms 7.94ms 63.00%
上面的示例总共发出了 100,000 个请求,使用 100 个并发客户端(即 100 个 HTTP/2 会话),每个客户端最多 100 个流。使用-t
选项, h2load
将使用多个本机线程以避免客户端的单个核心饱和。
警告
请勿针对公共服务器使用此工具。这被认为是 DOS 攻击。请仅将其用于您的私人服务器。
如果启用了实验性的 HTTP/3,h2load 可以向 HTTP/3 服务器发送请求。为此,请将h3
指定为--alpn-list
选项,如下所示:
$ h2load --alpn-list h3 https://127.0.0.1:4433
对于 nghttp2 v1.58 或更早版本,请使用--npn-list
而不是--alpn-list
。
src
目录包含 HPACK 工具。 deflatehd
程序是一个命令行标头压缩工具。 inflatehd
程序是一个命令行头解压工具。这两个工具从 stdin 读取输入并将输出写入 stdout。错误将写入 stderr。他们采用 JSON 作为输入和输出。我们(大部分)使用 https://github.com/http2jp/hpack-test-case 中描述的相同 JSON 数据格式。
deflatehd
程序从Stdin读取JSON数据或HTTP/1风格的标头字段,并在JSON中输出压缩标头块。
对于JSON输入,根JSON对象必须包括一个cases
键。它的值必须包括输入标头集的序列。它们共享相同的压缩环境,并按照出现的顺序进行处理。序列中的每个项目都是JSON对象,它必须包括headers
键。它的值是JSON对象的数组,它完全包括一个名称/值对。
例子:
{
"cases" :
[
{
"headers" : [
{ ":method" : " GET " },
{ ":path" : " / " }
]
},
{
"headers" : [
{ ":method" : " POST " },
{ ":path" : " / " }
]
}
]
}
使用-t
选项,该程序可以接受更熟悉的HTTP/1样式标头字段块。每个标头组都由一个空线界定:
例子:
:方法:获取
:方案:https
:小路: /
:方法:帖子
用户代理:NGHTTP2
输出在JSON对象中。它应该包括一个cases
密钥,其值是一系列JSON对象,至少具有以下键:
output_length
/ input_length
* 100示例:
{
"cases" :
[
{
"seq" : 0 ,
"input_length" : 66 ,
"output_length" : 20 ,
"percentage_of_original_size" : 30.303030303030305 ,
"wire" : " 01881f3468e5891afcbf83868a3d856659c62e3f " ,
"headers" : [
{
":authority" : " example.org "
},
{
":method" : " GET "
},
{
":path" : " / "
},
{
":scheme" : " https "
},
{
"user-agent" : " nghttp2 "
}
],
"header_table_size" : 4096
}
,
{
"seq" : 1 ,
"input_length" : 74 ,
"output_length" : 10 ,
"percentage_of_original_size" : 13.513513513513514 ,
"wire" : " 88448504252dd5918485 " ,
"headers" : [
{
":authority" : " example.org "
},
{
":method" : " POST "
},
{
":path" : " /account "
},
{
":scheme" : " https "
},
{
"user-agent" : " nghttp2 "
}
],
"header_table_size" : 4096
}
]
}
该输出可以用作inflatehd
和deflatehd
的输入。
使用-d
选项,添加了额外的header_table
键,其关联的值包括处理相应的标题集后的动态标头表的状态。该值至少包括以下密钥:
referenced
为true
,则在参考集中。 size
包括开销(32个字节)。该index
对应于标头表的索引。 name
是标题字段名称, value
是标题字段值。max_deflate_size
中占用的空间条目的总和。max_size
。在这种情况下,编码器仅用于first max_deflate_size
buffer。由于标题表的大小仍然是max_size
,因此编码器必须跟踪max_deflate_size
之外的条目,但在max_size
内部,并确保不再引用它们。例子:
{
"cases" :
[
{
"seq" : 0 ,
"input_length" : 66 ,
"output_length" : 20 ,
"percentage_of_original_size" : 30.303030303030305 ,
"wire" : " 01881f3468e5891afcbf83868a3d856659c62e3f " ,
"headers" : [
{
":authority" : " example.org "
},
{
":method" : " GET "
},
{
":path" : " / "
},
{
":scheme" : " https "
},
{
"user-agent" : " nghttp2 "
}
],
"header_table_size" : 4096 ,
"header_table" : {
"entries" : [
{
"index" : 1 ,
"name" : " user-agent " ,
"value" : " nghttp2 " ,
"referenced" : true ,
"size" : 49
},
{
"index" : 2 ,
"name" : " :scheme " ,
"value" : " https " ,
"referenced" : true ,
"size" : 44
},
{
"index" : 3 ,
"name" : " :path " ,
"value" : " / " ,
"referenced" : true ,
"size" : 38
},
{
"index" : 4 ,
"name" : " :method " ,
"value" : " GET " ,
"referenced" : true ,
"size" : 42
},
{
"index" : 5 ,
"name" : " :authority " ,
"value" : " example.org " ,
"referenced" : true ,
"size" : 53
}
],
"size" : 226 ,
"max_size" : 4096 ,
"deflate_size" : 226 ,
"max_deflate_size" : 4096
}
}
,
{
"seq" : 1 ,
"input_length" : 74 ,
"output_length" : 10 ,
"percentage_of_original_size" : 13.513513513513514 ,
"wire" : " 88448504252dd5918485 " ,
"headers" : [
{
":authority" : " example.org "
},
{
":method" : " POST "
},
{
":path" : " /account "
},
{
":scheme" : " https "
},
{
"user-agent" : " nghttp2 "
}
],
"header_table_size" : 4096 ,
"header_table" : {
"entries" : [
{
"index" : 1 ,
"name" : " :method " ,
"value" : " POST " ,
"referenced" : true ,
"size" : 43
},
{
"index" : 2 ,
"name" : " user-agent " ,
"value" : " nghttp2 " ,
"referenced" : true ,
"size" : 49
},
{
"index" : 3 ,
"name" : " :scheme " ,
"value" : " https " ,
"referenced" : true ,
"size" : 44
},
{
"index" : 4 ,
"name" : " :path " ,
"value" : " / " ,
"referenced" : false ,
"size" : 38
},
{
"index" : 5 ,
"name" : " :method " ,
"value" : " GET " ,
"referenced" : false ,
"size" : 42
},
{
"index" : 6 ,
"name" : " :authority " ,
"value" : " example.org " ,
"referenced" : true ,
"size" : 53
}