谈谈 HTTP/2 的协议协商机制


https://imququ.com/post/protocol-negotiation-in-http2.html#simple_thread

https://imququ.com/post/protocol-negotiation-in-http2.html#simple_thread


谈谈 HTTP/2 的协议协商机制

文章目录

  • HTTP Upgrade
  • ALPN 扩展
  • 小结

在过去的几个月里,我写了很多有关 HTTP/2 的文章,也做过好几场相关分享。我在向大家介绍 HTTP/2 的过程中,有一些问题经常会被问到。例如要部署 HTTP/2 一定要先升级到 HTTPS 么?升级到 HTTP/2 之后,不支持 HTTP/2 的浏览器还能正常访问么?本文重点介绍 HTTP/2 的协商机制,明白了服务端和客户端如何协商出最终使用的 HTTP 协议版本,这两个问题就迎刃而解了。

HTTP Upgrade

为了更方便地部署新协议,HTTP/1.1 引入了 Upgrade 机制,它使得客户端和服务端之间可以借助已有的 HTTP 语法升级到其它协议。这个机制在 RFC7230 的「6.7 Upgrade」这一节中有详细描述。

要发起 HTTP/1.1 协议升级,客户端必须在请求头部中指定这两个字段:

Connection: Upgrade

Upgrade: protocol-name[/protocol-version]

客户端通过 Upgrade 头部字段列出所希望升级到的协议和版本,多个协议之间用英文逗号和空格(0x2C, 0x20)隔开。除了这两个字段之外,一般每种新协议还会要求客户端发送额外的新字段,这里略过不写。

如果服务端不同意升级或者不支持 Upgrade 所列出的协议,直接忽略即可(当成 HTTP/1.1 请求,以 HTTP/1.1 响应);如果服务端同意升级,那么需要这样响应:

HTTP

HTTP/1.1 101 Switching Protocols

Connection: upgrade

Upgrade: protocol-name[/protocol-version]


[... data defined by new protocol ...]

可以看到,HTTP Upgrade 响应的状态码是 101,并且响应正文可以使用新协议定义的数据格式。

如果大家之前使用过 WebSocket,应该已经对 HTTP Upgrade 机制有所了解。下面是建立 WebSocket 连接的 HTTP 请求:

HTTP

GET ws://example.com/ HTTP/1.1

Connection: Upgrade

Upgrade: websocket

Origin: http://example.com

Sec-WebSocket-Version: 13

Sec-WebSocket-Key: d4egt7snxxxxxx2WcaMQlA==

Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

这是服务端同意升级的 HTTP 响应:

HTTP

HTTP/1.1 101 Switching Protocols

Connection: Upgrade

Upgrade: websocket

Sec-WebSocket-Accept: gczJQPmQ4Ixxxxxx6pZO8U7UbZs=

在这之后,客户端和服务端之间就可以使用 WebSocket 协议进行双向数据通讯,跟 HTTP/1.1 没关系了。可以看到,WebSocket 连接的建立就是典型的 HTTP Upgrade 机制。

显然,这个机制也可以用做 HTTP/1.1 到 HTTP/2 的协议升级。例如:

HTTP

GET / HTTP/1.1

Host: example.com

Connection: Upgrade, HTTP2-Settings

Upgrade: h2c

HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>

在 HTTP Upgrade 机制中,HTTP/2 的协议名称是 h2c,代表 HTTP/2 ClearText。如果服务端不支持 HTTP/2,它会忽略 Upgrade 字段,直接返回 HTTP/1.1 响应,例如:

HTTP

HTTP/1.1 200 OK

Content-Length: 243

Content-Type: text/html


...

如果服务端支持 HTTP/2,那就可以回应 101 状态码及对应头部,并且在响应正文中可以直接使用 HTTP/2 二进制帧:

HTTP

HTTP/1.1 101 Switching Protocols

Connection: Upgrade

Upgrade: h2c


[ HTTP/2 connection ... ]

以下是通过 HTTP Upgrade 机制将 HTTP/1.1 升级到 HTTP/2 的 Wireshark 抓包(两张图可以对照来看):



根据 HTTP/2 协议中的描述,额外补充几点:

  • 41 号包中,客户端发起的协议升级请求中,必须通过 HTTP2-Settings 指定一个经过 Base64 编码过的 HTTP/2 SETTINGS 帧;
  • 45 号包中,服务端同意协议升级,响应正文中必须包含 HTTP/2 SETTING 帧(二进制格式,不需要 Base64 编码);
  • 62 号包中,客户端可以开始发送各种 HTTP/2 帧,但第一个帧必须是 Magic 帧(内容固定为 PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n),做为协议升级的最终确认;

HTTP Upgrade 机制本身没什么问题,但很容易受网络中间环节影响。例如不能正确处理 Upgrade 头部的代理节点,很可能造成最终升级失败。之前我们统计过 WebSocket 的连通情况,发现大量明明支持 WebSocket 的浏览器却无法升级,只能使用降级方案。

ALPN 扩展

HTTP/2 协议本身并没有要求它必须基于 HTTPS(TLS)部署,但是出于以下三个原因,实际使用中,HTTP/2 和 HTTPS 几乎都是捆绑在一起:

  • HTTP 数据明文传输,数据很容易被中间节点窥视或篡改,HTTPS 可以保证数据传输的保密性、完整性和不被冒充;
  • 正因为 HTTPS 传输的数据对中间节点保密,所以它具有更好的连通性。基于 HTTPS 部署的新协议具有更高的连接成功率;
  • 当前主流浏览器,都只支持基于 HTTPS 部署的 HTTP/2;

如果前面两个原因还不足以说服你,最后这个绝对有说服力,除非你的 HTTP/2 服务只打算给自己客户端用。

下面介绍在 HTTPS 中,浏览器和服务端之间怎样协商是否使用 HTTP/2。

基于 HTTPS 的协议协商非常简单,多了 TLS 之后,双方必须等到成功建立 TLS 连接之后才能发送应用数据。而要建立 TLS 连接,本来就要进行 CipherSuite 等参数的协商。引入 HTTP/2 之后,需要做的只是在原本的协商机制中把对 HTTP 协议的协商加进去。

Google 在 SPDY 协议中开发了一个名为 NPN(Next Protocol Negotiation,下一代协议协商)的 TLS 扩展。随着 SPDY 被 HTTP/2 取代,NPN 也被官方修订为 ALPN(Application Layer Protocol Negotiation,应用层协议协商)。二者的目标和实现原理基本一致,这里只介绍后者。如图:


可以看到,客户端在建立 TLS 连接的 Client Hello 握手中,通过 ALPN 扩展列出了自己支持的各种应用层协议。其中,HTTP/2 协议名称是 h2


如果服务端支持 HTTP/2,在 Server Hello 中指定 ALPN 的结果为 h2 就可以了;如果服务端不支持 HTTP/2,从客户端的 ALPN 列表中选一个自己支持的即可。

并不是所有 HTTP/2 客户端都支持 ALPN,理论上建立 TLS 连接后,依然可以再通过 HTTP Upgrade 进行协议升级,只是这样会额外引入一次往返。

小结

看到这里,相信你一定可以很好地回答本文开头提出的问题。

HTTP/2 需要基于 HTTPS 部署是当前主流浏览器的要求。如果你的 HTTP/2 服务要支持浏览器访问,那就必须基于 HTTPS 部署;如果只给自己客户端用,可以不部署 HTTPS(这个页面列举了很多支持 h2c 的 HTTP/2 服务端、客户端实现)。

支持 HTTP/2 的 Web Server 基本都支持 HTTP/1.1。这样,即使浏览器不支持 HTTP/2,双方也可以协商出可用的 HTTP 版本,没有兼容性问题。如下表:

浏览器

服务器

协商结果

不支持 HTTP/2

不支持 HTTP/2

不协商,使用 HTTP/1.1

不支持 HTTP/2

支持 HTTP/2

不协商,使用 HTTP/1.1

支持 HTTP/2

不支持 HTTP/2

协商,使用 HTTP/1.1

支持 HTTP/2

支持 HTTP/2

协商,使用 HTTP/2

当然,本文讨论的是通用情况。对于自己实现的客户端和服务端,如果打算使用 HTTP/2 ClearText,由于 HTTP Upgrade 协商会增加一次往返,可以要求双方必须支持 HTTP/2,直接发送 HTTP/2 数据,不走协商。

本文链接:https://imququ.com/post/protocol-negotiation-in-http2.html参与评论 »

--EOF--

发表于 2016-04-14 00:14:19,并被添加「HTTPSHTTP/2」标签。查看本文 Markdown 版本 »

本站使用「署名 4.0 国际」创作共享协议,相关说明 »

提醒:本文最后更新于 204 天前,文中所描述的信息可能已发生改变,请谨慎使用。

专题「HTTP/2 相关」的其他文章 »

« 三种解密 HTTPS 流量的方法介绍

移动 WEB 通用优化策略介绍(一) »

Comments切换到评论完整模式

  • 31 Comments

本模式仅提供基础浏览功能。如需发表评论请点击这里(请确保你能流畅访问 disq.us | disquscdn.com | disqus.com)




  • SevenStar
     • 22天前
    請問你是如何查到各大瀏覽器是否支持h2c的呢





  • yaung
     • 1个月前
    是不是HTTP1.1 请求协议升级的话,只能升级到 h2c,不能升级到http/2 么?
  • 那么服务端同时兼容http1.1 与http/2 的话,因为主流浏览器不支持h2c ,那么服务端接收http1.1请求走的依然是普通的80端口么? THX





  • laike9m
     • 2个月前
    最后一句不太明白:由于 HTTP Upgrade 协商会增加一次往返,可以要求双方必须支持 HTTP/2,直接发送 HTTP/2 数据,不走协商。
    难道 settings 信息可以不发送么?如果 settings 信息必须发送的话,那么就像文中展示的一样,Upgrade 头直接是和 settings 一起发送的,并不会多一次往返吧。





    • Jerry Qu
       • 2个月前
      对于 HTTP/2 Cleartext 来说,客户端在不知道服务端是否支持 H2 的前提下,需要使用 HTTP/1.1 发起请求,然后服务端响应一个 101,客户端再发起 H2 请求,这就多了一次往返啊。





      • laike9m
         • 2个月前
      • GET / HTTP/1.1
      • Host: example.com
      • Connection: Upgrade, HTTP2-Settings
      • Upgrade: h2c
      • HTTP2-Settings: <base64url encoding="" of="" http="" 2="" settings="" payload="">
      • 我的意思是,客户端的 upgrade 请求里带了 HTTP2-Settings,然而这个 HTTP2-Settings 总是要发的(如果用 H2),所以并没有多一次吧





        • Jerry Qu
           • 2个月前
          如果直接用 H2,Settings 和 请求可以一次发出去啊。
        • 需要客户端用 HTTP/1 的 Upgrade,只能先发 Settings,服务端同意 Upgrade,客户端再发 H2 请求。





  • ladyk
     • 6个月前
    有个问题需要咨询下,我nginx升级到h2后,出现的现象是:
    现象一、ios、chrome 、firefox浏览器访问我的nginx,走的是h2 --没有问题
    现象二、在android 5.0 通过OkHttpClient访问https://imququ.comhttps://http2.golang.org/reqin... -- 没有问题
    现象三、在android 5.0 通过OkHttpClient访问我的nginx,没有走h2,走http1.1,--有问题
    查了帖子 http://stackoverflow.com/quest...
  • down vote accepted Okhttp 2.5+ only support http/2 above 5.0+ via ALPN.
    问题出现在哪里呢? 服务端nginx需要做什么配置吗,请协助分析下,谢谢。
    以下是我的配置信息:
    server {
  • listen 443 ssl http2;
  • server_name a.xxx.com b.xxx.com;
  • server_tokens off;
    # 中间证书 + 站点证书
  • ssl_certificate /usr/local/nginx/ssl/key-crt/chained.pem;
    # 创建 CSR 文件时用的密钥
  • ssl_certificate_key /usr/local/nginx/ssl/key-crt/domain.key;
    ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
  • ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_session_cache shared:SSL:50m;
  • ssl_session_timeout 1d;
    ssl_session_tickets on;
    resolver_timeout 10s;
    access_log logs/test.log;
    location / {
  • add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
  • add_header X-Frame-Options deny;
  • add_header X-Content-Type-Options nosniff;
    add_header Cache-Control no-cache;
    index index.html;}
  • }





  • 王琪亮
     • 7个月前
    了解了。
  • 话说h2c是不是目前常见的浏览器都不支持,也就是一定要h2 over TLS 1.2?





  • kn007
     • 7个月前
    了解了。想请教个问题,你现在博客有针对百度搜索做什么特殊化吗?比如兼容http?





    • Jerry Qu
       • 7个月前
      没有,实际上我的 nginx 配置都贴在了上上篇文章里。我的博客现在每天大约一半的流量从 Google 过来的。





      • kn007
         • 7个月前
        这半个月来的数据。
      • - Baidu 552 / 559
      • - Google 216 / 217
      • - Microsoft Bing 118 / 118
      • - SoGou 49 / 49
      • - DMOZ 1 / 1
      • - Google (Images) 1 / 1
      • - Yahoo! 1 / 1





      • kn007
         • 7个月前
        头疼的是,我那边的百度过来的流量是google的两倍多。导致我无法放弃百度,所以ssl的HSTS一直没开。我现在想逐步放弃这一部分流量,启用HSTS





  • man tou
     • 7个月前
    请问你得站点是怎么升级h2的,证书中没有看到ALPN的扩展,请求里面没有找到升级请求。是因为之前协商过了,浏览器默认发起h2的请求么。





    • Jerry Qu
       • 7个月前
      ALPN 扩展跟证书无关,是浏览器发起 TLS 握手时加上的,可以 wireshark 抓包看。





  • Gowe
     • 7个月前
    你好,有两点疑惑,不解,望能指教:
  • 1.对于 HTTP/2 的初始化存在一点疑问:
  • HTTP/2 是否必须由 HTTP/1.1 (或非 HTTP/2 的 HTTPS )经过协商后,Upgrade 来的吗?会不会有 B/S 两端都直接默认起始协议即为 HTTP/2 的情况呢?
    2.对于文中一点,存在疑问:
  • “不支持 HTTP/2 支持 HTTP/2 不协商,使用 HTTP/1.1”
  • 是否存在这种情况:客户端起始使用 HTTP/1.1 与服务器建立连接,且没有主动要求 Upgrade ,服务端通过 426 状态码协商 Upgrade 。





    • Jerry Qu
       • 7个月前
      我的理解是:
      1、可以的,本文最后一段说的就是这种情况。自己实现客户端时,如果知道服务端一定支持 HTTP/2,不用走协商。实际上,对于浏览器来说,如果一个网站之前成功协商到 HTTP/2,也可以在一段时间内不协商,直接发起 HTTP/2 请求,但是要做好回退处理。
      但现在的浏览器都不支持 HTTP/2 over HTTPS,所以直接走 TLS 的 ALPN 扩展就好了。
      2、在 HTTP/2 的协议文档里,只有针对本文这两种协商的描述:http://httpwg.org/specs/rfc754...
    • 我觉得服务端如果针对 HTTP/1.1 请求返回 426 Upgrade Required 的话,那实际上就是将不支持 HTTP/2 的浏览器拒之门外(因为如果客户端不在请求里带上 Upgrade 头部,服务端就无法得知客户端是否支持 HTTP/2),应该很少有这种需求吧?





      • Justfly Ho
         • 6个月前
        「因为如果客户端不在请求里带上 Upgrade 头部,服务端就无法得知客户端是否支持 HTTP/2」
        所以这里有个疑问,浏览器会访问任何一个网站都加上Upgrade吗?
        如果不加,浏览器是永远不会知道服务器支持HTTP/2的;
      • 如果每次都加,connection字段的其他值,比如keep-alive,就不能使用了,这不优雅。
        还是说有其他办法?





        • Jerry Qu
           • 6个月前
          如果浏览器已经与服务端建立了连接,那就不需要再协商,直接在这个连接上按之前约定好的协议发应用数据就可以。也就是说理论上浏览器只有在第一次与网站建立建立时,才需要带上 Upgrade。另外,Connection: keep-alive 实际上在 HTTP/1.1 中已经不存在了,只要没有明确指定 Connection: close,连接就是 keep-alive 的。
          当然实际上,目前所有浏览器都只支持 HTTP/2 Over TLS(通过在 TLS 的 Client Hello 握手中带上 h2 来完成协议协商)。所以,当前在浏览器中不存在通过 Upgrade 升级到 HTTP/2 的情况。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值