HTTP/2 探索第二篇——工具及应用

阅读数:2109 2019 年 8 月 22 日 20:16

HTTP/2探索第二篇——工具及应用

目录结构:
HTTP/2 环境搭建 Step by step
wireshark 使用
fiddler 使用(会持续更新)
nginx 的简单配置
h2o 服务器及其 Server Push 策略思想简介 —— h2o 实现的可感知缓存的 Server Push,我们借用终端的力量应该能实现的更好
curl —— 涉及到 HTTP2 的调试
nghttp —— 一个 HTTP2 客户端请求工具,可以显示出请求的 Frame
nghttpd —— 一个 HTTP2 服务器搭建工具,可以搭建可 Server Push 的服务器

软件安装
一、证书 —— 自签发证书和 Lets Encrypt 证书申请
二、基本工具
三、服务器准备
四、开发准备
SSL/TLS 部分小贴士
活用证书提高性能
HSTS 和 CSP 提高安全
系列文章
Reference

HTTP/2 环境搭建 Step by step

本文需要安装的软件为(Mac 用户请使用 homebrew 来安装,括号里都是 Mac 的安装方式)

curl (brew install nghttp2 —with-nghttp2)

wget https: //curl .haxx.se/download/curl-7.48.0.tar.gz

tar xvf curl-7.48.0.tar.gz && cd curl-7.48.0

然后输入以下命令,exit 一次再进入然后版本是 7.48 就成功鸟:

复制代码
$ ./configure --with-nghttp2=/usr/local --with-ssl
$ make
$ sudo make install
$ ldconfig
$ usr/local/bin/curl -V

Linux 安装

nghttp2 (brew install nghttp2)

安装网址

复制代码
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 \
libjemalloc-dev cython python3-dev python-setuptools

上面是安装依赖,之后就可以编译了:

复制代码
$ autoreconf -i
$ automake
$ autoconf
$ ./configure
$ make
$ sudo make install
nginx (brew install nginx —devel)

增加以下两行,其中 $codename 自行去该网站查询,比如 Ubuntu 14.04 是 trusty;

复制代码
deb http://nginx.org/packages/mainline/ubuntu/ $codename nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ $codename nginx

Linux 安装

vim 到 /etc/apt/sources.list

然后 sudo apt-get update && sudo apt-get install nginx 就可以装最新支持 HTTP2 的 nginx 啦;

apache (brew install httpd24 —with-http2)[本文重点讲解 nginx]

Linux 安装

一、证书

自签发
打开提供的 Demo 文件,terminal 打开 /keys 路径,输入./ca.sh http2test .com,也可以生成其他域名或泛域名(*.xx.com)。注意:也可以不用生成证书,直接使用 keys 文件下提供的 http2test.com 证书;

Let’s Encrypt

打开 Let’s Encrypt 的官网,这里讲解如果已经启动 nginx 的情况下,如何签发证书。

步骤:

首先需要把代码克隆下来:git clone https: //github. com/letsencrypt/letsencrypt && cd letsencrypt

然后./letsencrypt-auto --help 会执行一些初始化工作,并且显示支持的命令;

比如我的 web 目录在~/www 下,此步骤需要已搭建好服务器,并且可以通过 example.com 访问~/www 里的内容,如果是动态网站,需要在 nginx 层设置一个映射,将 /.well-known/acme-challenge 映射到刚设置好的目录,也就是~/www。

复制代码
location /.well-known/acme-challenge { root /home/$username/www;
}

(需要 nginx 已经装好,没装好的完成下面的 nginx 部分后再回来设置)那么./letsencrypt-auto certonly --webroot -w ~/www -d example. com,example.com 是你希望申请证书的域名,然后证书下发成功。

然后在 nginx 中设置证书信息

复制代码
ssl_certificate /etc/letsencrypt/live/$example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/$example.com/privkey.pem;

然后 sudo nginx -t && sudo nginx -s reload 就可以看到啦

二、基本工具

curl

curl 的基本用法是:

复制代码
curl -v -o /dev/null --http2 http://nghttp2.org

这里输入的是 HTTP 而不是 HTTPS 是因为,这里会采用 HTTP2 的 ClearText 模式,使用 101 改变协议协商升级为 HTTP2 协议。

复制代码
Connected to nghttp2.org (106.186.112.116) port 80 (#0)> GET / HTTP/1.1> Host: nghttp2.org
> User-Agent: curl/7.48.0> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAP__>
< HTTP/1.1 101 Switching Protocols
< Connection: Upgrade
< Upgrade: h2c
* Received 101* Using HTTP2, server supports multi-use* Connection state changed (HTTP/2 confirmed)
* TCP_NODELAY set
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=21* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
< HTTP/2.0 200< date:Sun, 10 Apr 2016 16:52:43 GMT
< content-type:text/html
< content-length:6646< last-modified:Sun, 03 Apr 2016 06:53:14 GMT
< etag:"5700bdda-19f6"< link:</stylesheets/screen.css>; rel=preload; as=stylesheet
< accept-ranges:bytes
< x-backend-header-rtt:0.000625< server:nghttpx nghttp2/1.10.0-DEV
< via:2 nghttpx
< x-frame-options:SAMEORIGIN
< x-xss-protection:1; mode=block
< x-content-type-options:nosniff
<

现在的大多数网站,都是通过 302 跳转到 HTTPS 网站来协商升级的,例如我厂的 QQ 邮箱;

复制代码
Connected to mail.qq.com (183.60.15.162) port 80 (#0)> GET / HTTP/1.1> Host: mail.qq.com> User-Agent: curl/7.48.0> Accept: */*> Connection: Upgrade, HTTP2-Settings> Upgrade: h2c> HTTP2-Settings: AAMAAABkAAQAAP__>
< HTTP/1.1 302 Found< Server: TWS< Connection: close< Date: Sun, 10 Apr 2016 16:58:48 GMT< Content-Type: text/html; charset=GB18030< Location: https://mail.qq.com/cgi-bin/loginpage< Content-Length: 0<

nghttp

官方文档,安装 nghttp2 后有配套的 nghttp 客户端、nghttpd 服务器、nghttpx 反向代理、h2load 负载测试等工具。

输入命令 nghttp -nv https: //nghttp2. org (n 代表不输出,v 代表详细信息):

结果列出了连接过程中的 HTTP2 各个 Stream 信息,例如 SETTINGS Frame,HEADER Frame 等,也可以带上参数:

复制代码
[ 0.170] Connected
The negotiated protocol: h2
[ 0.732] 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.732] 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.732] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.732] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[ 0.732] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[ 0.732] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[ 0.732] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[ 0.732] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
[ 0.732] send HEADERS frame <length=36, 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.9.1
[ 0.891] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.891] recv (stream_id=13) :method: GET
[ 0.891] recv (stream_id=13) :scheme: https
[ 0.892] recv (stream_id=13) :path: /stylesheets/screen.css
[ 0.892] recv (stream_id=13) :authority: nghttp2.org
[ 0.892] recv (stream_id=13) accept-encoding: gzip, deflate
[ 0.892] recv (stream_id=13) user-agent: nghttp2/1.9.1
[ 0.892] recv PUSH_PROMISE frame <length=47, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=2)
[ 0.892] recv (stream_id=13) :status: 200
[ 0.892] recv (stream_id=13) date: Sun, 10 Apr 2016 17:00:25 GMT
[ 0.892] recv (stream_id=13) content-type: text/html
[ 0.892] recv (stream_id=13) content-length: 6646
[ 0.892] recv (stream_id=13) last-modified: Sun, 03 Apr 2016 06:53:14 GMT
[ 0.892] recv (stream_id=13) etag: "5700bdda-19f6"
[ 0.892] recv (stream_id=13) link: </stylesheets/screen.css>; rel=preload; as=stylesheet
[ 0.892] recv (stream_id=13) accept-ranges: bytes
[ 0.892] recv (stream_id=13) x-backend-header-rtt: 0.000663
[ 0.892] recv (stream_id=13) strict-transport-security: max-age=31536000
[ 0.892] recv (stream_id=13) server: nghttpx nghttp2/1.10.0-DEV
[ 0.892] recv (stream_id=13) via: 2 nghttpx
[ 0.892] recv (stream_id=13) x-frame-options: SAMEORIGIN
[ 0.892] recv (stream_id=13) x-xss-protection: 1; mode=block
[ 0.892] recv (stream_id=13) x-content-type-options: nosniff
[ 0.892] recv HEADERS frame <length=266, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
[ 0.893] recv DATA frame <length=6646, flags=0x01, stream_id=13>
; END_STREAM
[ 0.893] recv (stream_id=2) :status: 200
[ 0.893] recv (stream_id=2) date: Sun, 10 Apr 2016 17:00:25 GMT
[ 0.893] recv (stream_id=2) content-type: text/css
[ 0.893] recv (stream_id=2) content-length: 39082
[ 0.893] recv (stream_id=2) last-modified: Sun, 03 Apr 2016 06:53:14 GMT
[ 0.893] recv (stream_id=2) etag: "5700bdda-98aa"
[ 0.893] recv (stream_id=2) accept-ranges: bytes
[ 0.893] recv (stream_id=2) x-backend-header-rtt: 0.000427
[ 0.893] recv (stream_id=2) strict-transport-security: max-age=31536000
[ 0.893] recv (stream_id=2) server: nghttpx nghttp2/1.10.0-DEV
[ 0.893] recv (stream_id=2) via: 2 nghttpx
[ 0.893] recv (stream_id=2) x-frame-options: SAMEORIGIN
[ 0.893] recv (stream_id=2) x-xss-protection: 1; mode=block
[ 0.893] recv (stream_id=2) x-content-type-options: nosniff
[ 0.893] recv (stream_id=2) x-http2-push: 1
[ 0.893] recv HEADERS frame <length=62, flags=0x04, stream_id=2>
; END_HEADERS
(padlen=0)
; First push response header
[ 1.407] recv DATA frame <length=16384, flags=0x00, stream_id=2>[ 1.563] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=33412)
[ 1.740] recv DATA frame <length=16384, flags=0x00, stream_id=2>[ 1.741] recv DATA frame <length=6314, flags=0x01, stream_id=2>
; END_STREAM
[ 1.741] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[])

还有这些参数:

复制代码
-v : 显示详细信息 -n : 不输出请求返回数据内容 -t : 请求超时设置 -w : 设置初始窗口大小 -a : 解析页面获取页面上的资源 -H : 给请求增加特定头部信息,例如 -H':method:PUT'-p : 设置请求权重 --no-dep : 不发送依赖信息

nghttpd

官方文档,使用 nghttpd,可以架设一个简易的支持 HTTP2 的服务器。打开附件中的示例文件,在路径 demos/nghttp/ 里有一个 run.sh(需要以管理员权限执行)

复制代码
$ cd demos/nghttp
$ sudo ./run.sh [server-push]

就可以执行提供的 shell 脚本,第三个参数 server-push 是可选的,如果不需要 server-push 功能,直接 sudo ./run.sh 就可以了,在本地设置好 host 127.0.0.1 http2test.com 后,就可以在本地浏览器中访问了,路径是 https://http2test.com/examples/dashboard/。由于自签的证书不被信任,需要安装公钥 keys/*.crt 文件并信任 (Mac 中需要打开 keychain 这个软件,然后找到刚安装的证书,改为总是信任),安装信任后,打开 Chrome 就不会提示警告了;

如果需要开启 server-push 功能,输入指令 sudo ./run.sh server-push,shell 脚本里设置了 -p/examples/dashboard/=/examples/dashboard/d3.js,当请求路径 /examples/dashboard/ 时,就推送 /examples/dashboard/d3.js 文件。

三、服务器准备

nginx

nginx 的 conf 文件里的设置,设置完后需要 sudo nginx -s reload,由于 nginx 的 ngx_http_v2_module 模块是替代以前的 ngx_http_spdy_module,故开启 HTTP2 支持后,就无法同时开启 SPDY 支持:

复制代码
server { listen 443 ssl http2; server_name http2test.com; ssl_certificate $ 证书地址 ; ssl_certificate_key $ 证书私钥地址 ; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { root $ 静态文件地址 ; index index.html index.htm;
}
}

h2o

这里引入下 Server Push 的概念,在上一篇概念篇有介绍道,Server Push 就是,当请求时,服务器同时将其它文件也推送过来,HTTP2 的说明中,并没有规定 Server Push 具体的实现策略,这个可由服务器和客户端自主决定:

Jetty:jetty 通过 referrer 建立了一个依赖模型,该模型可以应用在 CDN 里,方便配置;

h2o:通过 Cookie 实现了 cache aware server push——可感知缓存的服务器推送。第一次推送时,服务器端 set-cookie,将要推送的路径 +etag 存入一个集合,通过算法生产指纹编码为 base64 存入 cookie,之后通过 cookie 来判断是否需要推送该文件;

这里着重介绍 h2o,为什么 h2o 要这么做呢?因为流量就是钱啊。。。客户端接收到服务器端发送的 PUSHPROMISE Frame 后,可以通过是否发送 RST Frame 来拒绝掉服务器推送的文件,但是在这个过程中,服务器推送的文件可能已经发送一部分过来了,如果客户端决定不接收服务器推送的文件,那么之前发送的部分就是浪费掉的流量,h2o 就是为了解决这个问题;在设置好 h2o 后,我们来看看第一次访问的结果:

HTTP/2探索第二篇——工具及应用

让我们来看看第二次访问的结果:

HTTP/2探索第二篇——工具及应用

可以看到,第二次访问时,并没有 PUSHPROMISE Frame 了,也就是说,服务器不再推送文件了。

四、开发准备

wireshark

这里讲解基本的 wireshark 使用,由于大多数的 HTTP2 都是基于 Over TLS 版,也就是需要解密才能看到正确的 HTTP2 包内容;wireshark 提供了两个方式:

使用 SSLKEYLOGFILE,使得 wireshark 可以使用 Firefox/Chrome 的解密信息

首先在 wireshark 的 Preferences -> Protocol -> SSL 配置一个可读的文件地址,然后在环境变量,例如 Mac 是~/.bashprofile:

HTTP/2探索第二篇——工具及应用

export SSLKEYLOGFILE=$ 刚配置的地址
source ~/.bashprofile
通过 Terminal 打开 Firefox 或 Chrome,这样才能使用环境变量 SSLKEYLOGFILE:

open -a Google\ Chrome
该方法的局限是,只能在电脑上抓包使用,并且只能使用 Chrome 和 Firefox;

还有一个方法是配置 RSA 私钥,不过这种办法的局限是多种加密方式不支持,并且该方法必须纪录完整的 TLS 握手过程,所以建议打开浏览器访问前就已经开始监听;

配置好解密方式后,使用浏览器打开刚跑起来的 HTTP2 Demo,https: //http2test. com/examples/dashboard/,然后再筛选出输入 http2 并回车,就可以看到筛选的 HTTP2 包。

HTTP/2探索第二篇——工具及应用

fiddler

fiddler 解密 HTTPS 采用的是中间人攻击的方法,客户端访问到 fiddler 代理服务器,然后 fiddler 代理服务器再模拟客户端访问到网站。那么,需要安装 Fiddler 的根认证证书,同时启用 HTTPS 解密功能。

HTTP/2探索第二篇——工具及应用

对于 Fiddler 来说,对于每个机器其实下发的其实是不同的根证书,所以,一定要通过 Fiddler 的配置网页来安装证书(配置代理后,打开网站 http://ipv4.fiddler:8888,然后点击图中的 FiddlerRoot Certificate 来下载安装)

HTTP/2探索第二篇——工具及应用

针对 iOS 及部分高版本的 Android,证书中的部分信息是必要的,有两个办法。1) 采用如图所示的设置(iOS 9 亲测可用),2) 安装使用 Fiddler CertMaker 插件

HTTP/2探索第二篇——工具及应用

然后需要介绍的一个点是 FiddlerScript,这是一个很强大的功能。如果需要将以前调试 HTTP 的方法在 HTTPS 同样适用,需要有这些设置。部分设置可以参考 Qzone https+SPDY 实施 扩展篇:debug with https,然后之后我也会继续更新 Fiddler 调试部分的内容,请收藏哦。在 Willow 的 rule 里设置 HTTPS -> HTTP; HTTPS -> File; 都是可以生效的。

如果需要 HTTPS -> HTTPS 或者 HTTPS -> HTTP,可以采用上面链接的方法,也可以设置 oSession[“x-replywithtunnel”] = “FakeTunnel”。

复制代码
if (oSession.HostnameIs("app.yourdomain.com"))
{ // Handle CONNECT Tunnels
if (oSession.HTTPMethodIs("CONNECT"))
{
oSession["x-replywithtunnel"] = "FakeTunnel"; return;
}
oSession.fullUrl = "http://somedomain:someport" + oSession.PathAndQuery;
}

SSL/TLS 部分小贴士

一、活用证书,提高性能

首先来看本地搭建的 HTTPS 服务:

第一个情况是,同一个 IP,但是域名不同,同时,使用的证书也不同:第一张图显示的域名是 http2test. com,第二张图显示的域名是 http2 .icese7en. com,他们使用的证书也是不同的,可以看到 TCP Stream 这一列,具有不同的序号(相同序号表明他们使用的是同一个 TCP 连接)

HTTP/2探索第二篇——工具及应用HTTP/2探索第二篇——工具及应用

第二个情况是,同一个 IP,域名不同,但是使用的证书是相同的:第一张图的域名是 cdn1. http2test. com,第二张图的域名是 cdn2. http2test. com,他们使用的证书(第三张图)是泛域名 *.http2test. com,他们的 TCP Stream 序号都是 30,复用同一条 TCP 连接

HTTP/2探索第二篇——工具及应用HTTP/2探索第二篇——工具及应用

那么,如果域名不同,甚至都不是子域名的方式,也可以通过这一个点去优化吗?是可以的,通过证书里的 subjectAltName,我们可以在证书里管理多个不同域名,例如 google 的证书:

HTTP/2探索第二篇——工具及应用

不过这样证书很贵就是了 - -

二、SSL/TLS 提高安全

HSTS——HTTP 严格传输协议

HSTS 主要是为了改善以下几个问题:

没办法知道一个网站是否支持 TLS:例如第一次连接时,如果没有指定协议名,会优先采用 http 协议,有安全隐患。而开启 HSTS 后,第一次(之前访问过该域名)之后的访问浏览器会自动强制使用 HTTPS 访问;

对证书问题的宽容:未开启 HSTS 时,如果证书有问题,客户端只会提示警告,而不是禁止访问,开启 HSTS 后,会被禁止访问;

mixed-content 问题:开启 HSTS 后,一个 HTTPS 页面上的同域名下的非 HTTPS 资源会自动升级为 HTTPS 访问;

要开启 HSTS,只需要服务器端在返回头中返回(不支持 IP)

复制代码
Strict-Transport-Security: max-age=31536000 [; includeSubDomains] [; preload]# includeSubDomains 可以开启子域名的 HSTS 功能# 不过需要评估这么做的是否会有负面影响

在 max-age 指定的时间内,浏览器都自动以 HTTPS 访问。如果需要关闭 HSTS,返回 max-age=0 就可以了。

注意
HSTS 的第一次访问仍然具有被挟持的危险,可通过 HSTS preload 来减轻影响(需要申请加入各浏览器的 preload-list);

HSTS 时间是基于本地时间计算的,如果客户端时间被篡改,可能会造成 HSTS 策略被失效绕过。

引用自关于启用 HTTPS 的一些经验分享(一)

只要你不能确保永远提供 HTTPS 服务,就不要启用。因为一旦 HSTS 生效,你再想把网站重定向为 HTTP,之前的老用户会被无限重定向,唯一的办法是换新域名

CSP——内容安全政策

CSP 是一个声明式的安全机制,原本的目的是防止 XSS 跨站脚本攻击。例如,CSP 可以完全禁止内联的 Javascript 代码的执行,控制哪些外联的 Javascript 文件可以被执行。

复制代码
Content-Security-Policy: default-src 'self'; img-src *; script-src scripts.example.com

上面的 CSP 策略是,默认允许本源的资源的访问,允许所有 URL 的图片显示,外链脚本只允许来自于 scripts.example.com 的文件执行;

复制代码
Content-Security-Policy: default-src https: 'unsafe-inline' 'unsafe-eval'; connect-src https:

上面的 CSP 策略是,默认允许来自于任何 URL 的资源,只要它是安全的 HTTPS,同时开启了内联 JS 和 eval 的执行(默认是被 CSP 禁止的),connect-src 设定了 Ajax 请求只能请求 HTTPS;(部分开启内联 JS 的执行可参考 Content Security Policy Level 2 介绍)

CSP 上报

使用 report-uri 指令,违反 CSP 策略的行为都会被上报到该 CGI

本文转载自公众号小时光茶舍(ID:gh_7322a0f167b5)。

原文链接:

https://mp.weixin.qq.com/s/Y1x0GUkfRbWEz6YsJrMTAQ

收藏

评论

微博

用户头像
发表评论

注册/登录 InfoQ 发表评论