女主宣言

上一篇文章中介绍了HTTP/2的二进制分帧和多路复用的特性,这次来介绍下头部压缩和服务端推送。本文来自公众号“360搜索技术团队”的投稿,作者付坤。

PS:丰富的一线技术、多元化的表现形式,尽在“360云计算”,点关注哦!

HTTP/2 in GO(一)

上一篇文章中介绍了HTTP/2的二进制分帧和多路复用的特性,这次来介绍下头部压缩和服务端推送。

HTTP/2新增特性

  • 二进制分帧(HTTP Frames)

  • 多路复用

  • 头部压缩

  • 服务端推送(Server Push)

1

头部压缩

在HTTP/1.x中,每次HTTP请求都会携带需要的header信息,这些信息以纯文本形式传递,所以每次的请求和响应,都会浪费一些带宽,如果header信息中包含cookie等之类的信息,那么浪费的带宽就更可观了。为了减少带宽开销和提升性能,HTTP/2 使用 HPACK 压缩格式压缩请求和响应标头元数据,这种格式采用两种简单但是强大的技术:

  • 这种格式支持通过静态Huffman 编码对传输的header字段进行编码,从而减小了传输的大小。

  • 这种格式要求客户端和服务器同时维护和更新一个包含之前见过的header字段的索引列表(换句话说,它可以建立一个共享的压缩上下文),此列表随后会用作参考,对之前传输的值进行有效编码。

利用 Huffman 编码,可以在传输时对各个值进行压缩,而利用之前传输值的索引列表,我们可以通过传输索引值的方式对重复值进行编码,索引值可用于有效查询和重构完整的标头键值对。

客户端和服务端都有一个内置的静态表,部分内容如下:

静态表+-------+-----------------------------+---------------+| Index | Header Name                 | Header Value  |+-------+-----------------------------+---------------+| 1     | :authority                  |               || 2     | :method                     | GET           || 3     | :method                     | POST          || 4     | :path                       | /             || 5     | :path                       | /index.html   || 6     | :scheme                     | http          || 7     | :scheme                     | https         || 8     | :status                     | 200           || 9     | :status                     | 204           || 10    | :status                     | 206           || 11    | :status                     | 304           || 12    | :status                     | 400           || 13    | :status                     | 404           || 14    | :status                     | 500           || 15    | accept-charset              |               || 16    | accept-encoding             | gzip, deflate || 17    | accept-language             |               |...| 58    | user-agent                  |               || 59    | vary                        |               || 60    | via                         |               || 61    | www-authenticate            |               |+-------+-----------------------------+---------------+          

可以看到,部分静态表已经包含了value,比如 Index=2 的 :method = GET,当客户端发起请求时,如果发起的是GET请求,那么只需要在Header信息中携带一个Index=2的索引即可,服务端收到通过静态表即可查出对应的请求头信息。 
在静态表中传输的Header Block是这种格式的:

     0   1   2   3   4   5   6   7   +---+---+---+---+---+---+---+---+   | 1 |        Index (7+)         |   +---+---------------------------+

从图中可以看到,只需要8-bit即可实现一个method的Header传输:

with index

对于静态表中不存在value的值,或者value的值跟想传递的值不一样时,就不能只传递简单的Index了;比如对于:path的头信息,如果要请求的path不在静态表里,就需要用到 Huffman 编码 了。 
假设:path的值为/post/20180811-http2_in_go_1.html

     0   1   2   3   4   5   6   7   +---+---+---+---+---+---+---+---+   | 0 | 0 | 0 | 0 |  Index (4+)   |   +---+---+-----------------------+   | H |     Value Length (7+)     |   +---+---------------------------+   | Value String (Length octets)  |   +-------------------------------+

从下图中能看到,占用了25个Byte来传递:path=/post/20180811-http2_in_go_1.html的信息,这个数值比传输明文字符串要节省空间;当然是由于这些字符串普遍在 Huffman 编码 的压缩比较高的字典里,经过编码后会占用较小的空间,如果要传输的都是一个比较奇怪的字符,那么也有可能出现编码后占用的空间比之前还要高。

那如果我们应用要传输的就是一些奇怪的字符串,难道我们要每次传输比直接更大的值么,其实不然。除了静态表HPACK 算法还提供了一个 动态表,双方针对每个connection共同维护这个表,这样对于之前未出现过的Header信息,只要传输一次,那么下次大家就都了解了。

比如下边这个user-agent,第一次传输时,Index是从本地静态表获取,传输给服务端后,会把Header Name+Header Value同时更新到本地的动态表里;这样本地和服务端都同时存在一个相同id的动态表了,这里大家都追加到了Index=76动态表,再次传递时,只要跟静态表的结构一样即可。占用1Byte就ok。

第一次传输

第二次传输

最后总结下,用Roberto Peon(HPACK的设计者之一)的话说: 
HPACK旨在提供一个一致性的实现使信息量的损失尽可能少,使编解码快速而方便,使接收方能控制压缩文本的大小,允许代理重新建立索引(如,通过代理在前后端共享状态),以及对哈夫曼编码串的更快速比较”

2

服务端推送(Server Push)

Server Push指的是服务端主动向客户端推送数据,相当于对客户端的一次请求,服务端可以主动返回多次结果。这个功能打破了严格的请求---响应的语义,对客户端和服务端双方通信的互动上,开启了一个崭新的可能性。但是这个推送跟websocket中的推送功能不是一回事,Server Push的存在不是为了解决websocket推送的这种需求。

对我们的web应用来说,举个最简单的例子,有一个index.html页面:

<!DOCTYPE html><html><head>  <link rel="stylesheet" href="push-style.css"></head><body>  <h1>hello, server push</h1>  <img src="push-image.png"></body></html>
  • 在HTTP/1.x里,为了展示这个页面,客户端会先发起一次 GET /index.html 的请求,拿到返回结果进行分析后,再发起两个资源的请求,一共是三次请求, 并且有串行的请求存在。

  • 在HTTP/2里,当客户端发起 GET /index.html的请求后,如果服务端进行了Server Push的支持,那么会直接把客户端需要的/index.html和另外两份文件资源一起返回,避免了串行和多次请求的发送。

大家可以看看Go官方给的这个Server Push的例子:


这个功能的实现,主要就依赖于上一篇文章提到的PUSH_PROMISE Frame,所有的推送请求,都是有 PUSH_PROMISE 来发起,服务端通过向客户端在返回正常的Response前,优先发送 PUSH_PROMISE,来表达自己即将为客户端推送的资源,当客户端收到请求后,针对这些资源,就不会再向服务端发起请求。

+---------------+ |Pad Length? (8)| +-+-------------+-----------------------------------------------+ |R|                  Promised Stream ID (31)                    | +-+-----------------------------+-------------------------------+ |                   Header Block Fragment (*)                 ... +---------------------------------------------------------------+ |                           Padding (*)                       ... +---------------------------------------------------------------+

PUSH_PROMISE中,包含了一个Promised Stream ID,这个是服务端承诺向客户端推送相关数据时使用的Stream IDHeader Block中包含资源链接等相关内容。 
客户端收到PUSH_PROMISE后,可以选择接受服务器推送的资源,如果客户端发现本地缓存已经存在,不需要服务端再推送,也可以向对应的Stream ID发送RST_STREAM帧,来阻止服务端发送Push.

下边看几张用h2c(不是ClearText的h2c,是一个HTTP/2 Command-Line Client)模拟http2请求的图来看效果,还是访问的上边Go官方给的Server Push的网页:

在发起 GET /serverpush的请求后,收到了服务端发送的PUSH_PROMISE,承诺在2、4、6、8等stream ID的流中给发送相应的资源信息:


然后可以看到,在Stream Id为2、4、6、8的流上开始给客户端发送Header帧和Data帧的数据,顺序不定,同时也在向Get /serverpush这个stream ID=1的流上返回相关的页面信息。


最后,就是各个Stream传输自己的Data数据,直到数据传输完毕,打上END_STREAM的Flag表明流传输结束。


Stream 状态机

说完这两个HTTP/2的特性,对整体概念应该有所了解了,最后说下Stream 状态机,就容易理解了。

这个状态图从客户端和服务端两方面分别来展示的,大家可以先自己看下,图下方有发送标记的解释:

                             +--------+                     send PP |        | recv PP                    ,--------|  idle  |--------.                   /         |        |         \                  v          +--------+          v           +----------+          |           +----------+           |          |          | send H /  |          |    ,------| reserved |          | recv H    | reserved |------.    |      | (local)  |          |           | (remote) |      |    |      +----------+          v           +----------+      |    |          |             +--------+             |          |    |          |     recv ES |        | send ES     |          |    |   send H |     ,-------|  open  |-------.     | recv H   |    |          |    /        |        |        \    |          |    |          v   v         +--------+         v   v          |    |      +----------+          |           +----------+      |    |      |   half   |          |           |   half   |      |    |      |  closed  |          | send R /  |  closed  |      |    |      | (remote) |          | recv R    | (local)  |      |    |      +----------+          |           +----------+      |    |           |                |                 |           |    |           | send ES /      |       recv ES / |           |    |           | send R /       v        send R / |           |    |           | recv R     +--------+   recv R   |           |    | send R /  `----------->|        |<-----------'  send R / |    | recv R                 | closed |               recv R   |    `----------------------->|        |<----------------------'                             +--------+

       send:   endpoint sends this frame       recv:   endpoint receives this frame

       H:  HEADERS frame (with implied CONTINUATIONs)       PP: PUSH_PROMISE frame (with implied CONTINUATIONs)       ES: END_STREAM flag       R:  RST_STREAM frame

其中,half closed状态,就是进入Server Push后的状态,有一方,其实就是客户端,进入了半开闭状态,这时候它不能通过这个Stream再发送请求的相关数据,只能接受数据,或者选择结束链接。half closed状态可以由idle状态经过两条路径到达:

  • 服务端发送PUSH_PROMISE后发送Header帧信息,使客户端进入半开闭

  • 客户端通过Header帧向服务端发送数据,并在Header标记END_STREAM的Flag,表明自己期望结束Stream,不再向服务端发送HeaderData的数据,这个时候服务端还没同意关闭Stream,所以服务端是可以向客户端发送数据的。

这里边比较奇怪的就是reservedhalf closed两个状态,看起来没有什么区别,通过一个无关紧要的Header帧来触发状态转换。 
其实,在Go语言里,对HTTP/2的Stream State的实现,就是把reservedhalf closed当做一个状态给合并了。

那么为什么会有这两个状态呢,其实是出于Stream Concurrency并发的限制,在并发限制里,reserved状态不计入活跃状态,不进行限制。这样能达到的一个效果就是,即使Stream并发数达到限制以后,服务端仍然是能向客户端发送PUSH_PROMISE的,能够一定程度的防止PUSH_PROMISE不能发送而导致的客户端竞争请求。

这块我也是简单介绍下,想了解更仔细的话,可以参考这篇文章RFC7540 笔记(四)——More on Stream States。

好了,本次就说这些。下次开始介绍HTTP/2在Go语言中的一些实现和用法。

最后再提供一个HTTP/2开发相关的工具介绍,方便调试和开发:

  • HTTP/2相关tools

页面内对一些技术点都有标记链接,如果希望可以深入了解,可以点查看原文进行查看。

相关文章

  • Rfc-7541: https://tools.ietf.org/html/rfc7541

  • laike9m's blog-RFC7540 笔记(四)——More on Stream States: https://laike9m.com/blog/rfc7540-bi-ji-si-more-on-stream-states,105/

360云计算

由360云平台团队打造的技术分享公众号,内容涉及数据库、大数据、微服务、容器、AIOps、IoT等众多技术领域,通过夯实的技术积累和丰富的一线实战经验,为你带来最有料的技术分享

HTTP/2 in GO(二)相关推荐

  1. 条形码?二维码?生成、解析都在这里!

    二维码生成与解析 一.生成二维码 二.解析二维码 三.生成一维码 四.全部的代码 五.pom依赖 直接上代码: 一.生成二维码 public class demo {private static fi ...

  2. 【ReactiveX】基于Golang pmlpml/RxGo程序包的二次开发

    基于Golang pmlpml/RxGo程序包的二次开发[阅读时间:约20分钟] 一.ReactiveX & RxGo介绍 1.ReactiveX 2.RxGo 二.系统环境&项目介绍 ...

  3. 2022-2028年中国二次供水产业发展动态及投资战略规划报告

    [报告类型]产业研究 [报告价格]4500起 [出版时间]即时更新(交付时间约3个工作日) [发布机构]智研瞻产业研究院 [报告格式]PDF版 本报告介绍了中国二次供水行业市场行业相关概述.中国二次供 ...

  4. 2022-2028年中国二次供水设备行业研究及前瞻分析报告

    [报告类型]产业研究 [报告价格]4500起 [出版时间]即时更新(交付时间约3个工作日) [发布机构]智研瞻产业研究院 [报告格式]PDF版 本报告介绍了中国二次供水设备行业市场行业相关概述.中国二 ...

  5. OpenCV 笔记(08)— 二维点、三维点、基于 Mat 的 std::vector 等常用数据结构的定义和输出

    1. 定义和输出二维点 Point2f p2(3, 4);cout << "[二维点] is "<< endl << p2 << e ...

  6. python内置库之学习ctypes库(二)

    ctypes库踩坑日记2 一.自己实现一个dll文件,再用python的ctypes库调用思路1更清晰 二.生成dll文件 三.ctypes库调用 一.自己实现一个dll文件,再用python的cty ...

  7. 前端Vue学习之路(二)-Vue-router路由

    Vue学习之路 (二) Vue-router(基础版) 一.增加静态路由 二.动态路由+路由嵌套+404页面 三. 编程式导航 四.命名路由 五.命名视图 六.重定向和起别名 1.重定向 2.起别名 ...

  8. Bert代码详解(二)重点

    这是bert的pytorch版本(与tensorflow一样的,这个更简单些,这个看懂了,tf也能看懂),地址:https://github.com/huggingface/pytorch-pretr ...

  9. word2vec 中的数学原理详解(二)预备知识

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/peghoty/article/details/37969635 https://blog.csdn. ...

  10. LeetCode简单题之二叉搜索树的最小绝对差/最小距离

    题目 给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 . 差值是一个正数,其数值等于两值之差的绝对值. 示例 1: 输入:root = [4,2,6,1,3] 输出: ...

最新文章

  1. Petrozavodsk Summer-2016. Ural FU Dandelion Contest
  2. 如何理解 Graph Convolutional Network (GCN)?
  3. 刚入门的UI设计师,需要懂的图标设计规范?
  4. 继承体系下的对象构造
  5. 345.反转字符串中的元音字符(力扣leetcode) 博主可答疑该问题
  6. PHP+MYSQL图书管理系统(课设)
  7. 加密狗映射至虚拟服务器,XenServer6.x U盘、加密狗等USB设备映射到VM虚拟机教程.pdf...
  8. AtCoder题解——Beginner Contest 168——E - ∙ (Bullet)
  9. Python天天美味(15) - Python正则表达式操作指南(re使用)(转)
  10. html数字自动滚动代码,HTML+JS实现滚动数字的时钟
  11. 博士申请 | 美国埃默里大学招收2022秋季入学机器学习方向全奖博士生
  12. 操作系统——多处理器和多核架构
  13. linux 合并多个pdf,Linux 下合并 PDF
  14. Spring的学习之路(必看)
  15. LinkButton的样式设置(背景图片问题)
  16. 移动SEO之页面优化
  17. html网页里如何竖着打字,搜狗输入法怎么设置为竖排显示 怎样把横向打字变成竖向...
  18. python代码圣诞树你还没有嘛?所有画法都在这篇文章里拉~
  19. centos7下安装无头浏览器(headless Chrome)
  20. 股指期货高频数据机器学习预测

热门文章

  1. extjs $ 美元符号修改
  2. Javascript 操作元素Class属性的问题
  3. mysql workbench for ubuntu测试
  4. SpringBoot集成MyBatis-Plus框架详细方法
  5. if函数判断单元格颜色_excel中的if函数,实现自动判断
  6. 犯病的chorme(谷歌浏览器)之css加载失败
  7. php制作的ios端 跳转url,ThinkPHP 简易开发思路 MVC和URL跳转
  8. 专家:未来三年为转云时间窗口
  9. 压缩js和css, IIS开启Etags, IIS开启Gzip
  10. cocos2d-x的popScene的动画效果