nghttp2 - HTTP/2 C语言库
这是一个用C语言实现的第二版超文本传输协议。
HTTP/2的框架层被实现为一个可重用的C库。在此基础上,我们实现了一个HTTP/2客户端、服务器和代理。我们还为HTTP/2开发了负载测试和基准测试工具。
一个HPACK编码器和解码器可以作为公共API使用。
一个实验性的高级C++库也是可用的。
我们有这个库的Python绑定,但我们还没有完整的代码覆盖。
开发状态
我们已经实现了RFC 7540HTTP/2和RFC 7541HPACK - HTTP/2的头压缩。
nghttp2的代码库是从spdylay(https://github.com/tatsuhiro-t/spdylay)项目中分叉出来的。
公共测试服务器
以下终端可用于尝试我们的nghttp2实现。
-
nghttp2.org/(TLS + ALPN/NPN 和 HTTP/3)
这个端点通过ALPN/NPN支持
h2,h2-16,h2-14, 和http/1.1,并且需要TLSv1.2的HTTP/2连接。它也支持HTTP/3。
-
nghttp2.org/(HTTP升级和HTTP/2直接)
h2c和 。http/1.1
要求
构建 libnghttp2 库需要以下软件包。
- pkg-config >= 0.20
要构建和运行单元测试程序,需要以下软件包。
- cunit >= 2.1
为了构建文档,你需要安装。
- sphinx(http://sphinx-doc.org/)
如果你只需要 libnghttp2(C 库),那么上述软件包就是你所需要的。使用--enable-lib-only 来确保只有 libnghttp2 被构建。这可以避免与构建捆绑的应用程序有关的潜在构建错误。
要构建和运行src 目录下的应用程序 (nghttp,nghttpd,nghttpx 和h2load) ,需要以下软件包。
- OpenSSL >= 1.0.1
- libev >= 4.11
- zlib >= 1.2.3
- libc-ares >= 1.7.5
ALPN支持需要OpenSSL >= 1.0.2(2015年1月22日发布)。LibreSSL >= 2.2.0可以代替OpenSSL,但在写这篇文章时,OpenSSL的功能比LibreSSL多。
为了在nghttp 中启用-a 选项(从下载的资源中获取链接资产),需要以下软件包。
- libxml2 >= 2.6.26
要在 nghttpx 中启用 systemd 支持,需要以下软件包。
- libsystemd-dev >= 209
HPACK工具需要以下软件包。
- jansson >= 2.5
要在examples目录下构建源代码,需要libevent。
- libevent-openssl >= 2.0.8
为了减轻长期运行的服务器程序的堆碎片(nghttpd 和nghttpx),推荐使用 jemalloc。
-
jemalloc
注意
由于Musl的限制,Alpine Linux目前不支持malloc替换。详情见问题#762。
libnghttp2_asio C++库需要以下软件包。
- libboost-dev >= 1.54.0
- libboost-thread-dev >= 1.54.0
Python 绑定需要以下软件包。
- cython >= 0.19
- python >= 3.8
- python-setuptools
如果你使用的是Ubuntu 16.04 LTS (Xenial Xerus)或Debian 8 (jessie)及以上版本,运行以下程序来安装所需的软件包。
sudo apt-get install g++ make binutils autoconf automake autotools-dev libtool pkg-config \
zlib1g-dev libcunit1-dev libssl-dev libxml2-dev libev-dev libevent-dev libjansson-dev \
libc-ares-dev libjemalloc-dev libsystemd-dev \
cython python3-dev python-setuptools
为了使mruby支持nghttpx,mruby是必需的。我们需要在明确打开C++ ABI的情况下构建mruby,可能还需要其他的mrgems,mruby是由第三方/mruby目录下的git子模块管理的。目前,mruby对nghttpx的支持默认是禁用的。要启用mruby支持,请使用--with-mruby configure选项。注意,在写这篇文章的时候,Debian/Ubuntu的libmruby-dev和mruby包不能用于nghttp2,因为它们没有启用C++ ABI。要构建mruby,需要以下软件包。
- ruby
- bison
nghttpx支持neverbleed,OpenSSL/LibreSSL的权限分离引擎。简而言之,当像Heartbleed这样的严重错误被利用时,它可以将私钥泄露的风险降到最低。neverbleed在默认情况下是禁用的。要启用它,请使用--with-neverbleed configure选项。
要启用h2load和nghttpx的实验性HTTP/3支持,需要以下库。
- 支持QUIC的OpenSSL;或BoringSSL(提交36a41bf0bf2dd3176f8780e09c03585351f29963)
- ngtcp2>= 0.5.0
- nghttp3>= 0.4.0
使用--enable-http3 配置选项为h2load和nghttpx启用HTTP/3功能。
为了建立可选的eBPF程序,将传入的QUIC UDP数据报引导到nghttpx的正确套接字,需要以下库。
- libbpf-dev >= 0.7.0
使用--with-libbpf configure选项来构建eBPF程序。libelf-dev是构建libbpf所需要的。
对于Ubuntu 20.04,你可以从源代码中构建libbpf。 nghttpx需要eBPF程序来重新加载其配置和热交换其可执行文件。
编译 libnghttp2 的 C 源代码需要一个 C99 编译器,已知 gcc 4.8 已经足够。为了编译 C++ 源代码,需要 gcc >= 6.0 或 clang >= 6.0。C++源代码需要C++14的语言特性。
注意事项
要在nghttpx中启用mruby支持,请使用--with-mruby configure选项。
注意
Mac OS X用户可能需要--disable-threads configure选项来禁用nghttpd、nghttpx和h2load中的多线程,以防止它们崩溃。欢迎打一个补丁来使多线程在Mac OS X平台上工作。
注意事项
要编译相关的应用程序(nghttp、nghttpd、nghttpx和h2load),你必须使用--enable-app configure选项,并确保满足上述指定的要求。通常情况下,configure脚本会检查所需的依赖关系来构建这些应用程序,并自动启用--enable-app ,所以你不必明确使用它。但是如果你发现应用程序没有被构建,那么使用--enable-app 可能会找到这个原因,比如缺少依赖性。
注意
为了检测第三方库,我们使用了 pkg-config (然而,对于某些库(例如 libev),我们不使用 pkg-config)。默认情况下,pkg-config 会在标准位置搜索*.pc 文件(例如,/usr/lib/pkgconfig)。如果有必要使用自定义位置的*.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 。
从发布的 tar 档案中构建 nghttp2
nghttp2项目定期发布包括nghttp2源代码和生成的构建文件的tar档案。它们可以从Releases页面下载。
从 git 构建 nghttp2 需要 autotools 开发包。从焦油档案中构建不需要它们,因此要容易得多。通常的构建步骤是这样的。
$ tar xf nghttp2-X.Y.Z.tar.bz2
$ cd nghttp2-X.Y.Z
$ ./configure
$ make
从git构建
从 git 构建很容易,但请确保至少使用了 autoconf 2.68。
$ git submodule update --init
$ autoreconf -i
$ automake
$ autoconf
$ ./configure
$ make
在Windows(MSVC)上构建的注意事项
构建本地Windows nghttp2 dll的最简单方法是使用cmake。免费版的Visual C++ Build Tools可以很好地工作。
- 安装cmake for windows
- 打开 "Visual C++ ...Native Build Tool Command Prompt",并在nghttp2里面直接运行
cmake。 - 然后运行
cmake --build来构建库。 - nghttp2.dll, nghttp2.lib, nghttp2.exp都放在lib目录下。
注意,上述步骤很可能只产生 nghttp2 库。没有捆绑的应用程序被编译。
在Windows(Mingw/Cygwin)上编译时的注意事项
在Mingw环境下,你只能编译库,它的libnghttp2-X.dll 和libnghttp2.a 。
如果你想编译应用程序(h2load,nghttp,nghttpx,nghttpd ),你需要使用Cygwin环境。
在Cygwin环境下,要编译应用程序,你需要先编译和安装libev。
其次,你需要取消对宏__STRICT_ANSI__ 的定义,如果你不这样做,fdopen,fileno 和strptime 的功能将不可用。
像这样的示例命令。
$ export CFLAGS="-U__STRICT_ANSI__ -I$libev_PREFIX/include -L$libev_PREFIX/lib"
$ export CXXFLAGS=$CFLAGS
$ ./configure
$ make
如果你想在examples/ 下编译应用程序,你需要从libev的安装中删除或重命名event.h ,因为它与libevent的安装冲突。
在Linux系统上安装的注意事项
在用make install 安装 nghttp2 工具套件后,可能会遇到类似的错误。
nghttpx: error while loading shared libraries: libnghttp2.so.14: cannot open shared object file: No such file or directory
这意味着该工具无法找到libnghttp2.so 共享库的位置。
要更新共享库的缓存,请运行sudo ldconfig 。
构建文档
注意
文档仍然是不完整的。
要建立文档,请运行。
$ make html
文档将在doc/manual/html/ 下生成。
生成的文档将不会与make install 一起安装。
在线文档可在nghttp2.org/documentati…
构建支持HTTP/3的h2load和nghttpx
要构建启用了HTTP/3功能的h2load和ngtpx,请用--enable-http3 ,运行配置脚本。
对于nghttpx重新加载配置和交换其可执行文件,同时优雅地终止旧的工作进程,eBPF是必需的。用--enable-http3 --with-libbpf 运行配置脚本,建立eBPF程序。QUIC密钥材料必须用--frontend-quic-secret-file ,以便在重载期间保持现有连接的活力。
构建启用HTTP/3的h2load和nghttpx的详细步骤如下。
构建自定义的OpenSSL。
$ git clone --depth 1 -b OpenSSL_1_1_1o+quic https://github.com/quictls/openssl
$ cd openssl
$ ./config --prefix=$PWD/build --openssldir=/etc/ssl
$ make -j$(nproc)
$ make install_sw
$ cd ..
构建nghttp3。
$ git clone --depth 1 -b v0.4.1 https://github.com/ngtcp2/nghttp3
$ cd nghttp3
$ autoreconf -i
$ ./configure --prefix=$PWD/build --enable-lib-only
$ make -j$(nproc)
$ make install
$ cd ..
构建 ngtcp2。
$ git clone --depth 1 -b v0.5.0 https://github.com/ngtcp2/ngtcp2
$ cd ngtcp2
$ autoreconf -i
$ ./configure --prefix=$PWD/build --enable-lib-only \
PKG_CONFIG_PATH="$PWD/../openssl/build/lib/pkgconfig"
$ make -j$(nproc)
$ make install
$ cd ..
如果你的Linux发行版没有libbpf-dev >= 0.7.0,请从源码构建。
$ git clone --depth 1 -b v0.7.0 https://github.com/libbpf/libbpf
$ cd libbpf
$ PREFIX=$PWD/build make -C src install
$ cd ..
构建nghttp2。
$ git clone https://github.com/nghttp2/nghttp2
$ cd nghttp2
$ git submodule update --init
$ autoreconf -i
$ ./configure --with-mruby --with-neverbleed --enable-http3 --with-libbpf \
--disable-python-bindings \
CC=clang-12 CXX=clang++-12 \
PKG_CONFIG_PATH="$PWD/../openssl/build/lib/pkgconfig:$PWD/../nghttp3/build/lib/pkgconfig:$PWD/../ngtcp2/build/lib/pkgconfig:$PWD/../libbpf/build/lib64/pkgconfig" \
LDFLAGS="$LDFLAGS -Wl,-rpath,$PWD/../openssl/build/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/3部分 - HTTP/2代理 - HOW-TO。
单元测试
单元测试是通过简单地运行make check 来完成的。
集成测试
我们有nghttpx代理服务器的集成测试。这些测试是用Go编程语言编写的,并使用其测试框架。我们依赖于以下的库。
- golang.org/x/net/http2
- golang.org/x/net/websocket
- github.com/tatsuhiro-t…
Go模块会自动下载这些依赖项。
要运行测试,在integration-tests 目录下运行以下命令。
$ make it
在测试中,我们使用3009端口来运行测试主体服务器。
从v0.7.15或更早的版本迁移
nghttp2 v1.0.0引入了几个向后不兼容的变化。在本节中,我们将描述这些变化以及如何迁移到v1.0.0。
ALPN协议的ID现在是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_MAGICNGHTTP2_CLIENT_CONNECTION_PREFACE_LEN被替换为 。NGHTTP2_CLIENT_MAGIC_LENNGHTTP2_BAD_PREFACE被改名为NGHTTP2_BAD_CLIENT_MAGIC
已经废弃的NGHTTP2_CLIENT_CONNECTION_HEADER 和NGHTTP2_CLIENT_CONNECTION_HEADER_LEN 被删除。
如果应用程序使用这些宏,只需用新的宏替换旧的宏。从v1.0.0开始,客户端魔法由库发送(见下一小节),所以客户端应用程序可以直接删除这些宏的使用。
客户端魔法是由库发送的
以前,ghttp2库不发送客户端魔法,也就是客户端连接前言的前24个字节,客户端应用程序必须自己发送它。从v1.0.0开始,客户端魔法由库通过第一次调用nghttp2_session_send() 或nghttp2_session_mem_send() 来发送。
发送客户端魔法的客户端应用程序必须删除相关代码。
删除HTTP替代服务(Alt-Svc)相关代码
Alt-Svc规范还没有最终确定。为了使我们的API稳定,我们决定从nghttp2中删除所有Alt-Svc相关的API。
NGHTTP2_EXT_ALTSVC已删除。nghttp2_ext_altsvc已被删除。
我们已经在v0.7系列中删除了Alt-Svc的功能,它们基本上已经没有了。使用这些宏和结构的应用程序,删除这些行。
在nghttp2_on_invalid_frame_recv_callback中使用nghttp2_error
以前nghttp2_on_invalid_frame_recv_cb_called ,把error_code ,定义在nghttp2_error_code ,作为参数。但它们不够详细,无法进行调试。因此,我们决定使用更详细的nghttp2_error 值来代替。
使用这个回调的应用程序应该更新回调签名。如果它将error_code 作为HTTP/2的错误代码,请更新该代码,使其被视为nghttp2_error 。
默认接收客户端的魔法
以前,ghttp2不处理客户端的魔术(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 - client
nghttp 是一个HTTP/2客户端。它可以通过事先了解、HTTP升级和NPN/ALPN TLS扩展连接到HTTP/2服务器。
它具有用于框架信息的verbose输出模式。下面是nghttp 客户端的输出样本。
$ nghttp -nv https://nghttp2.org
[ 0.190] Connected
The negotiated protocol: h2
[ 0.212] recv SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.212] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.212] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.212] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[ 0.212] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[ 0.212] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[ 0.212] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[ 0.212] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
[ 0.212] send HEADERS frame <length=39, flags=0x25, stream_id=13>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=11, weight=16, exclusive=0)
; Open new stream
:method: GET
:path: /
:scheme: https
:authority: nghttp2.org
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.0.1-DEV
[ 0.221] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.221] recv (stream_id=13) :method: GET
[ 0.221] recv (stream_id=13) :scheme: https
[ 0.221] recv (stream_id=13) :path: /stylesheets/screen.css
[ 0.221] recv (stream_id=13) :authority: nghttp2.org
[ 0.221] recv (stream_id=13) accept-encoding: gzip, deflate
[ 0.222] recv (stream_id=13) user-agent: nghttp2/1.0.1-DEV
[ 0.222] recv PUSH_PROMISE frame <length=50, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=2)
[ 0.222] recv (stream_id=13) :status: 200
[ 0.222] recv (stream_id=13) date: Thu, 21 May 2015 16:38:14 GMT
[ 0.222] recv (stream_id=13) content-type: text/html
[ 0.222] recv (stream_id=13) last-modified: Fri, 15 May 2015 15:38:06 GMT
[ 0.222] recv (stream_id=13) etag: W/"555612de-19f6"
[ 0.222] recv (stream_id=13) link: </stylesheets/screen.css>; rel=preload; as=stylesheet
[ 0.222] recv (stream_id=13) content-encoding: gzip
[ 0.222] recv (stream_id=13) server: nghttpx nghttp2/1.0.1-DEV
[ 0.222] recv (stream_id=13) via: 1.1 nghttpx
[ 0.222] recv (stream_id=13) strict-transport-security: max-age=31536000
[ 0.222] recv HEADERS frame <length=166, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
[ 0.222] recv DATA frame <length=2601, flags=0x01, stream_id=13>
; END_STREAM
[ 0.222] recv (stream_id=2) :status: 200
[ 0.222] recv (stream_id=2) date: Thu, 21 May 2015 16:38:14 GMT
[ 0.222] recv (stream_id=2) content-type: text/css
[ 0.222] recv (stream_id=2) last-modified: Fri, 15 May 2015 15:38:06 GMT
[ 0.222] recv (stream_id=2) etag: W/"555612de-9845"
[ 0.222] recv (stream_id=2) content-encoding: gzip
[ 0.222] recv (stream_id=2) server: nghttpx nghttp2/1.0.1-DEV
[ 0.222] recv (stream_id=2) via: 1.1 nghttpx
[ 0.222] recv (stream_id=2) strict-transport-security: max-age=31536000
[ 0.222] recv HEADERS frame <length=32, flags=0x04, stream_id=2>
; END_HEADERS
(padlen=0)
; First push response header
[ 0.228] recv DATA frame <length=8715, flags=0x01, stream_id=2>
; END_STREAM
[ 0.228] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[])
HTTP升级是这样进行的。
$ nghttp -nvu http://nghttp2.org
[ 0.011] Connected
[ 0.011] HTTP Upgrade request
GET / HTTP/1.1
Host: nghttp2.org
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: AAMAAABkAAQAAP__
Accept: */*
User-Agent: nghttp2/1.0.1-DEV
[ 0.018] HTTP Upgrade response
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
[ 0.018] HTTP Upgrade success
[ 0.018] recv SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.018] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.018] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.018] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[ 0.018] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[ 0.018] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[ 0.018] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[ 0.018] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
[ 0.018] send PRIORITY frame <length=5, flags=0x00, stream_id=1>
(dep_stream_id=11, weight=16, exclusive=0)
[ 0.019] recv (stream_id=1) :method: GET
[ 0.019] recv (stream_id=1) :scheme: http
[ 0.019] recv (stream_id=1) :path: /stylesheets/screen.css
[ 0.019] recv (stream_id=1) host: nghttp2.org
[ 0.019] recv (stream_id=1) user-agent: nghttp2/1.0.1-DEV
[ 0.019] recv PUSH_PROMISE frame <length=49, flags=0x04, stream_id=1>
; END_HEADERS
(padlen=0, promised_stream_id=2)
[ 0.019] recv (stream_id=1) :status: 200
[ 0.019] recv (stream_id=1) date: Thu, 21 May 2015 16:39:16 GMT
[ 0.019] recv (stream_id=1) content-type: text/html
[ 0.019] recv (stream_id=1) content-length: 6646
[ 0.019] recv (stream_id=1) last-modified: Fri, 15 May 2015 15:38:06 GMT
[ 0.019] recv (stream_id=1) etag: "555612de-19f6"
[ 0.019] recv (stream_id=1) link: </stylesheets/screen.css>; rel=preload; as=stylesheet
[ 0.019] recv (stream_id=1) accept-ranges: bytes
[ 0.019] recv (stream_id=1) server: nghttpx nghttp2/1.0.1-DEV
[ 0.019] recv (stream_id=1) via: 1.1 nghttpx
[ 0.019] recv HEADERS frame <length=157, flags=0x04, stream_id=1>
; END_HEADERS
(padlen=0)
; First response header
[ 0.019] recv DATA frame <length=6646, flags=0x01, stream_id=1>
; END_STREAM
[ 0.019] recv (stream_id=2) :status: 200
[ 0.019] recv (stream_id=2) date: Thu, 21 May 2015 16:39:16 GMT
[ 0.019] recv (stream_id=2) content-type: text/css
[ 0.019] recv (stream_id=2) content-length: 38981
[ 0.019] recv (stream_id=2) last-modified: Fri, 15 May 2015 15:38:06 GMT
[ 0.019] recv (stream_id=2) etag: "555612de-9845"
[ 0.019] recv (stream_id=2) accept-ranges: bytes
[ 0.019] recv (stream_id=2) server: nghttpx nghttp2/1.0.1-DEV
[ 0.019] recv (stream_id=2) via: 1.1 nghttpx
[ 0.019] recv HEADERS frame <length=36, flags=0x04, stream_id=2>
; END_HEADERS
(padlen=0)
; First push response header
[ 0.026] recv DATA frame <length=16384, flags=0x00, stream_id=2>
[ 0.027] recv DATA frame <length=7952, flags=0x00, stream_id=2>
[ 0.027] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=33343)
[ 0.032] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=2>
(window_size_increment=33707)
[ 0.032] recv DATA frame <length=14645, flags=0x01, stream_id=2>
; END_STREAM
[ 0.032] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.032] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[])
使用-s 选项,nghttp 打印出一些请求的时间信息,按完成时间排序。
$ nghttp -nas https://nghttp2.org/
***** Statistics *****
Request timing:
responseEnd: the time when last byte of response was received
relative to connectEnd
requestStart: the time just before first byte of request was sent
relative to connectEnd. If '*' is shown, this was
pushed by server.
process: responseEnd - requestStart
code: HTTP status code
size: number of bytes received as response body without
inflation.
URI: request URI
see http://www.w3.org/TR/resource-timing/#processing-model
sorted by 'complete'
id responseEnd requestStart process code size request path
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 - 服务器
nghttpd 是一个多线程的静态Web服务器。
默认情况下,它使用SSL/TLS连接。使用--no-tls 选项来禁用它。
nghttpd 只接受通过NPN/ALPN或直接的HTTP/2连接。不支持HTTP升级。
-p 选项允许用户配置服务器推送。
就像nghttp ,它有一个粗略的输出模式来获取框架信息。下面是nghttpd 的输出示例。
$ nghttpd --no-tls -v 8080
IPv4: listen 0.0.0.0:8080
IPv6: listen :::8080
[id=1] [ 1.521] send SETTINGS frame <length=6, flags=0x00, stream_id=0>
(niv=1)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[id=1] [ 1.521] recv SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[id=1] [ 1.521] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[id=1] [ 1.521] recv PRIORITY frame <length=5, flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[id=1] [ 1.521] recv PRIORITY frame <length=5, flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[id=1] [ 1.521] recv PRIORITY frame <length=5, flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[id=1] [ 1.521] recv PRIORITY frame <length=5, flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[id=1] [ 1.521] recv PRIORITY frame <length=5, flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
[id=1] [ 1.521] recv (stream_id=13) :method: GET
[id=1] [ 1.521] recv (stream_id=13) :path: /
[id=1] [ 1.521] recv (stream_id=13) :scheme: http
[id=1] [ 1.521] recv (stream_id=13) :authority: localhost:8080
[id=1] [ 1.521] recv (stream_id=13) accept: */*
[id=1] [ 1.521] recv (stream_id=13) accept-encoding: gzip, deflate
[id=1] [ 1.521] recv (stream_id=13) user-agent: nghttp2/1.0.0-DEV
[id=1] [ 1.521] recv HEADERS frame <length=41, flags=0x25, stream_id=13>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=11, weight=16, exclusive=0)
; Open new stream
[id=1] [ 1.521] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[id=1] [ 1.521] send HEADERS frame <length=86, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
:status: 200
server: nghttpd nghttp2/1.0.0-DEV
content-length: 10
cache-control: max-age=3600
date: Fri, 15 May 2015 14:49:04 GMT
last-modified: Tue, 30 Sep 2014 12:40:52 GMT
[id=1] [ 1.522] send DATA frame <length=10, flags=0x01, stream_id=13>
; END_STREAM
[id=1] [ 1.522] stream_id=13 closed
[id=1] [ 1.522] recv GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])
[id=1] [ 1.522] closed
nghttpx - proxy
nghttpx 是一个用于HTTP/3、HTTP/2和HTTP/1.1的多线程反向代理,为nghttp2.org,并支持HTTP/2服务器推送。
我们重新设计了nghttpx 的命令行界面,因此有几个与1.8.0或更早的版本不兼容的地方。这对于扩展其功能是必要的,并确保在未来的版本中进一步增强功能。请阅读Migrationfrom nghttpx v1.8.0 or earlierto know how to migrate from earlier releases.
nghttpx 实现了TLS中重要的面向性能的特性,例如会话ID、会话票据(带自动密钥轮换)、OCSP钉书、动态记录大小、ALPN/NPN、前向保密和HTTP/2。 ,还提供了通过memcached在多个 实例中共享会话缓存和票据密钥的功能。nghttpx 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升级升级到HTTP/2。另一方面,后端连接在默认情况下是不加密的。要加密后端连接,请使用--backend 选项中的tls 关键字。
nghttpx 支持一个配置文件。参见 选项和配置文件样本 。--conf nghttpx.conf.sample
在默认模式下,nghttpx 作为反向代理工作到后端服务器。
Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1, HTTP/2) --> Web Server
[reverse proxy]
使用--http2-proxy 选项,它作为前向代理工作,它是所谓的安全HTTP/2代理。
Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1) --> Proxy
[secure proxy] (e.g., Squid, ATS)
上述例子中的Client ,需要配置为使用nghttpx 作为安全代理。
在写这篇文章的时候,Chrome和Firefox都支持安全HTTP/2代理。配置Chrome浏览器使用安全代理的一种方法是创建一个proxy.pac脚本,像这样。
function
SERVERADDR 和 是运行nghttpx的机器的主机名/地址和端口。请注意,Chrome的安全代理需要一个有效的证书。PORT
然后用以下参数运行Chrome。
$ google-chrome --proxy-pac-url=file:///path/to/proxy.pac --use-npn
后端HTTP/2连接可以通过HTTP代理进行隧道连接。该代理是用--backend-http-proxy-uri.下图说明了nghttpx如何通过HTTP代理与外部的HTTP/2代理对话。
Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/2) --
--===================---> HTTP/2 Proxy
(HTTP proxy tunnel) (e.g., nghttpx -s)
基准测试工具
h2load 程序是HTTP/3、HTTP/2和HTTP/1.1的一个基准测试工具。h2load 的用户界面在很大程度上受到weighttp (https://github.com/lighttpd/weighttp)的启发。其典型用法如下。
$ h2load -n100000 -c100 -m100 https://localhost:8443/
starting benchmark...
spawning thread #0: 100 concurrent clients, 100000 total requests
Protocol: TLSv1.2
Cipher: ECDHE-RSA-AES128-GCM-SHA256
Server Temp Key: ECDH P-256 256 bits
progress: 10% done
progress: 20% done
progress: 30% done
progress: 40% done
progress: 50% done
progress: 60% done
progress: 70% done
progress: 80% done
progress: 90% done
progress: 100% done
finished in 771.26ms, 129658 req/s, 4.71MB/s
requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored
status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 3812300 bytes total, 1009900 bytes headers, 1000000 bytes data
min max mean sd +/- sd
time for request: 25.12ms 124.55ms 51.07ms 15.36ms 84.87%
time for connect: 208.94ms 254.67ms 241.38ms 7.95ms 63.00%
time to 1st byte: 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 到--npn-list 选项。
$ h2load --npn-list h3 https://127.0.0.1:4433
HPACK工具
src 目录包含HPACK工具。deflatehd 程序是一个命令行头压缩工具。inflatehd 程序是一个命令行的头解压缩工具。这两个工具都从stdin读取输入,并将输出写到stdout。错误则写到stderr。它们将JSON作为输入和输出。我们(大部分)使用相同的JSON数据格式,描述于github.com/http2jp/hpa…
deflatehd - 头部压缩器
deflatehd 程序从stdin读取JSON数据或HTTP/1风格的头域,并输出压缩的JSON头块。
对于JSON输入,根JSON对象必须包括一个cases key。它的值必须包括输入头组的序列。它们共享相同的压缩上下文,并按照它们出现的顺序进行处理。序列中的每一项都是一个JSON对象,它必须包括一个headers key。它的值是一个JSON对象的数组,其中正好包括一个名/值对。
例子。
{
使用-t 选项,程序可以接受更熟悉的HTTP/1风格的头域块。每个头域组都由一个空行分隔。
例子。
:method: GET
:scheme: https
:path: /
:method: POST
user-agent: nghttp2
输出是在JSON对象中。它应该包括一个cases ,其值是一个JSON对象的数组,它至少有以下的键。
seq
输入中头集的索引。
input_length
输入中的名/值对的长度之和。
output_length
压缩后的头块的长度。
原始尺寸的百分比
output_length / * 100input_length
线路
压缩后的头块是一个十六进制的字符串。
头部
输入的头文件集。
header_table_size
在对头文件集进行放气前调整的头文件表大小。
例子。
{
输出可以作为inflatehd 和deflatehd 的输入。
使用-d 选项,额外的header_table 键被添加,它的相关值包括在处理了相应的头集后动态头表的状态。该值至少包括以下键。
条目
头表中的条目。如果referenced 是true ,它就在参考集中。size 包括开销(32字节)。index 对应于头表的索引。name 是头域名称,value 是头域值。
大小
所占空间条目的总和,这包括条目开销。
max_size
头表的最大尺寸。
deflate_size
max_deflate_size 内占用的空间条目的总和。
max_deflate_size
编码器使用的最大头表尺寸。这可以小于max_size 。在这种情况下,编码器只使用到第一个max_deflate_size 缓冲区。由于头表的大小仍然是max_size ,编码器必须跟踪在max_deflate_size 外但在max_size 内的条目,并确保它们不再被引用。
例子。
{
inflatehd - 头部解压器
inflatehd 程序从stdin读取JSON数据,并在JSON中输出解压的名/值对。
根JSON对象必须包括cases 关键。它的值必须包括压缩头块的序列。它们共享相同的压缩环境,并按照它们出现的顺序进行处理。序列中的每一项都是一个JSON对象,它必须至少有一个wire key。它的值是一个压缩头块的十六进制字符串。
例子。
{
输出是一个JSON对象。它应该包括一个cases key,它的值是一个JSON对象的数组,它至少有以下的key。
seq
输入中的头文件集的索引。
头信息
一个JSON数组,包括解压缩的名/值对。
线
压缩后的头块,是一个十六进制的字符串。
header_table_size
在压缩头块前调整的头表大小。
例子。
{
输出可以作为deflatehd 和inflatehd 的输入。
使用-d 选项,额外的header_table 键被添加,其相关的值包括处理了相应的头集后动态头表的状态。其格式与deflatehd 相同。
libnghttp2_asio:高水平的 HTTP/2 C++ 库
libnghttp2_asio是建立在libnghttp2之上的C++库,为构建HTTP/2应用程序提供了高水平的抽象API。它依赖于Boost::ASIO库和OpenSSL。目前,libnghttp2_asio同时提供客户端和服务器端的API。
libnghttp2_asio默认不被构建。使用--enable-asio-lib configure标志来构建libnghttp2_asio。需要的Boost库有
- Boost::Asio
- Boost::System
- Boost::Thread
服务器API的设计是为了非常容易地建立一个HTTP/2服务器,以利用C++14匿名函数和闭包。一个HTTP/2服务器的最基本例子是这样的。
#
下面是使用客户端API的示例代码。
#
更多细节请参见 libnghttp2_asio 的文档。
Python 绑定
python 目录包含 nghttp2 Python 绑定。这些绑定目前提供了HPACK压缩器和解压器类以及一个HTTP/2服务器。
扩展模块被称为nghttp2 。
make 将构建绑定,目标Python版本由 脚本决定。如果检测到的Python版本不是你所期望的,可以在 变量中指定Python可执行文件的路径,作为配置脚本的参数(例如: )。configure PYTHON ./configure PYTHON=/usr/bin/python3.8
下面的示例代码说明了在Python中HPACK压缩器和解压器的基本用法。
import
nghttp2.HTTP2Server 类建立在asyncio的事件循环之上。在构建时,必须给出RequestHandlerClass,它必须是nghttp2.BaseRequestHandler 类的一个子类。
BaseRequestHandler 类被用来处理HTTP/2流。默认情况下,它什么都不做。它必须被子类化以处理每个事件的回调方法。
第一个被调用的回调方法是on_headers() 。当包括请求头字段的HEADERS帧到达时,它被调用。
如果请求有一个请求体,on_data(data) ,对每个收到的数据块进行调用。
一旦整个请求被接收,on_request_done() 被调用。
当流被关闭时,on_close(error_code) 被调用。
应用程序可以使用send_response() 方法发送一个响应。它可以在on_headers() 、on_data() 或on_request_done() 中使用。
应用程序可以使用push() 方法推送资源。它必须在调用send_response() 之前使用。
以下的实例变量是可用的。
client_address
包含一个形式为(host, port)的元组,指的是客户端的地址。
stream_id
此流的流ID。
方案
请求URI的方案。这是 :scheme header字段的一个值。
方法
这个流的方法。这是:method header字段的一个值。
主机
这是:authority或host头域的一个值。
路径
这是 :path header字段的一个值。
下面的例子说明了HTTP2Server和BaseRequestHandler的用法。
#!/usr/bin/env python3
贡献
[这段文字是根据1.2.1的内容编写的。curl/libcurl项目的许可部分。]
当贡献代码时,你同意将你的修改和新的代码放在 nghttp2 已经使用的相同的许可下,除非另有说明和同意。
当改变现有的源代码时,不要改变原始文件的版权。版权仍然属于原创作者或被原作者分配了版权的人。
通过向ngtp2项目提交补丁,你(或你的雇主,视情况而定)同意将你提交的版权转让给我们。......上面的内容真的需要重新措辞,以通过法律的考验。我们将尽可能地把你的修改记在你的名下,这样做不仅是为了给你面子,也是为了追溯到谁做了什么修改。请在投稿时向我们提供你的真实姓名。
更多细节请参见贡献指南。
报告漏洞
如果你在我们的软件中发现了漏洞,请发送电子邮件到 "tatsuhiro.t at gmail dot com",说明其细节,而不是在github问题页面上提交问题。标准的做法是,在固定版本发布之前,或缓解措施制定之后,不公开披露漏洞信息。
在未来,我们可能会为此目的设置一个专门的邮件地址。
发布时间表
一般来说,我们遵循语义版本学。我们每个月都会发布MINOR版本的更新,通常我们会在每个月的第25天左右发布它。
我们可能会在常规版本之间发布PATCH版本,主要是为了修复严重的安全漏洞。
我们没有计划打破涉及soname bump的API兼容性变化,所以在可预见的未来,MAJOR版本将保持1。
许可证
MIT许可证