nghttp2 / nghttp2

2,518 阅读21分钟

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

为了构建文档,你需要安装。

如果你只需要 libnghttp2(C 库),那么上述软件包就是你所需要的。使用--enable-lib-only 来确保只有 libnghttp2 被构建。这可以避免与构建捆绑的应用程序有关的潜在构建错误。

要构建和运行src 目录下的应用程序 (nghttp,nghttpd,nghttpxh2load) ,需要以下软件包。

  • 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

为了减轻长期运行的服务器程序的堆碎片(nghttpdnghttpx),推荐使用 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支持,需要以下库。

使用--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可以很好地工作。

  1. 安装cmake for windows
  2. 打开 "Visual C++ ...Native Build Tool Command Prompt",并在nghttp2里面直接运行cmake
  3. 然后运行cmake --build 来构建库。
  4. nghttp2.dll, nghttp2.lib, nghttp2.exp都放在lib目录下。

注意,上述步骤很可能只产生 nghttp2 库。没有捆绑的应用程序被编译。

在Windows(Mingw/Cygwin)上编译时的注意事项

在Mingw环境下,你只能编译库,它的libnghttp2-X.dlllibnghttp2.a

如果你想编译应用程序(h2load,nghttp,nghttpx,nghttpd ),你需要使用Cygwin环境。

在Cygwin环境下,要编译应用程序,你需要先编译和安装libev。

其次,你需要取消对宏__STRICT_ANSI__ 的定义,如果你不这样做,fdopen,filenostrptime 的功能将不可用。

像这样的示例命令。

$ 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编程语言编写的,并使用其测试框架。我们依赖于以下的库。

Go模块会自动下载这些依赖项。

要运行测试,在integration-tests 目录下运行以下命令。

$ make it

在测试中,我们使用3009端口来运行测试主体服务器。

从v0.7.15或更早的版本迁移

nghttp2 v1.0.0引入了几个向后不兼容的变化。在本节中,我们将描述这些变化以及如何迁移到v1.0.0。

ALPN协议的ID现在是h2h2c

之前我们公布了h2-14h2c-14 。v1.0.0实现了最终的协议版本,我们将ALPN ID改为h2h2c 。宏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_HEADERNGHTTP2_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.1http/1.1, http/2反向代理
--http2-proxyhttp/3、http/2、http/1.1http/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

在对头文件集进行放气前调整的头文件表大小。

例子。

{
  

输出可以作为inflatehddeflatehd 的输入。

使用-d 选项,额外的header_table 键被添加,它的相关值包括在处理了相应的头集后动态头表的状态。该值至少包括以下键。

条目

头表中的条目。如果referencedtrue ,它就在参考集中。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

在压缩头块前调整的头表大小。

例子。

{
  

输出可以作为deflatehdinflatehd 的输入。

使用-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许可证