HTTP/2 in GO(五)--大结局

相关阅读:

通过前边四章,我们了解了HTTP/2的特性,以及如何在Go中利用HTTP/2的相关特性进行一些开发工作。本章作为一个收尾,我来谈一些自己对HTTP/2的理解,以及HTTP/2的应用前景展望,个人观点,不一定对,欢迎大家留言讨论。

先回顾下:

HTTP/2新增特性

  • 二进制分帧(HTTP Frames)
  • 多路复用
  • 头部压缩
  • 服务端推送(server push)

HTTP/2通过多种多种技术手段(如:多路复用,头部压缩,优先级等),极大的优化了HTTP的C/S双端的数据交互体验,解决很多以往HTTP/1.1协议本身不能解决的问题。

HTTP/2的优势

  • 低延迟的内容传输(多路复用,优化RTT)
  • 带宽占用减小(头部压缩,编码,HPACK)
  • 连接数减少(多路复用,二进制分帧)

先来看下带宽和延迟对页面加载的影响,数据来源:HTTP/2 is here, let’s optimize! - Velocity SC 2015

  • 在5M以下的带宽内,对页面加载速度影响较大,5M以上的带宽,对页面加载速度影响较小。
  • 延迟的减少对页面加载时间的提升呈线性增长

阅读全文

HTTP/2 in GO(四)

相关阅读:

Start

上篇文章我们了解了如何在HTTP/2 server端进行Header信息的发送,同时保持连接不断开。这次我们在这个基础上,实现自动下发PUSH

先来实现一个最简单的Server Push例子, 我们在上次的demo基础上继续改进

package main

import (
	"html/template"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("X-custom-header", "custom header")
		w.WriteHeader(http.StatusNoContent)

		if f, ok := w.(http.Flusher); ok {
			f.Flush()
		}
		select {}
	})

	// 用于push的 handler
	http.HandleFunc("/crt", func(w http.ResponseWriter, r *http.Request) {
		tpl := template.Must(template.ParseFiles("server.crt"))
		tpl.Execute(w, nil)
	})

	// 请求该Path会触发Push
	http.HandleFunc("/push", func(w http.ResponseWriter, r *http.Request) {
		pusher, ok := w.(http.Pusher)
		if !ok {
			log.Println("not support server push")
		} else {
			err := pusher.Push("/crt", nil)
			if err != nil {
				log.Printf("Failed for server push: %v", err)
			}
		}
		w.WriteHeader(http.StatusOK)
	})

	log.Println("start listen on 8080...")
	log.Fatal(http.ListenAndServeTLS(":8080", "server.crt", "server.key", nil))
}

以上代码添加了两个Hanlder,一个是 /crt,返回我们的证书内容,这个是用来给做客户端push的内容。另一个是 /push,请求该链接时,我们会将 /crt 的内容主动 push 到客户端。

GO服务启动后,我们通过h2c来访问下/push : 先在一个终端通过 h2c start -d 启动进行输出显示,然后另外开一个终端窗口发起请求 h2c connect localhost:8080h2c get /push :

阅读全文

HTTP/2 in GO(三)

相关阅读:

Start

前边两章讲了很多HTTP/2概念性的东西,看起来比较无趣,从这次开始,我们从一些实际用途开始讲起。
本次讲一个非常简单的功能,然后把其内部实现串一下。
这次要实现的功能非常简单,就是一个http2的server,对客户端的请求,只返回一个header信息,并且保持连接,以便在后续任何时候进行一些其他的响应操作。目前看起来这个场景可能没有太大作用,其实HTTP/2做为一个超文本传输协议,目前我们能想到的应用场景还都是普通的web业务,但是老外们的思路就比较广,已经把一些HTTP/2的特性在特定的场景发挥出来了,比如 Amazon的Alexa,Apple的APNS 等。这次实现的这个小功能,就是Alexa里用到的一小部分.
Amazon的avs(Alexa Voice Service)通过HTTP/2实现了全双工的传输功能,其下行功能就用到了这块,Alexa跟avs建立链接后,客户端会发起一个GET /v20160207/directives的请求,服务端接受请求后,返回一个200的头信息,并hold住链接,后续使用该链接通过Server Push功能给客户端主动发送指令。
本次开始,我们先不管Server Push,先从发送Header这个小功能开始吧。

HTTP/2在GO语言的实现中没有支持h2c,所以我们必须使用带证书的加密方式,那么首先需要有一张证书。
我们可以使用openssl自己生成一张:

openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt

然后按提示随便输入一些内容就可以得到两个文件,server.keyserver.crt,其实就是相当于私钥和公钥。当然这个证书是不能在互联网上正常流通使用的,因为证书是自己签发的,没有人给你做担保,能确认这个证书跟它所标识的内容提供方是匹配的。所以我们在做请求测试的时候,需要客户端忽略证书校验才可以。

服务端GO示例的代码如下:

package main

import (
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("X-custom-header", "custom header")
		w.WriteHeader(http.StatusNoContent)

		if f, ok := w.(http.Flusher); ok {
			f.Flush()
		}
		select {}
	})

	log.Println("start listen on 8080...")
	log.Fatal(http.ListenAndServeTLS(":8080", "server.crt", "server.key", nil))
}

服务运行起来后我们在一个较新的支持HTTP/2的curl命令下执行:

curl  "https://localhost:8080/header"  -k -i --http2
  • -k 参数表示忽略证书校验,避免客户端拿到证书后校验不通过而拒绝链接
  • -i 参数表示显示返回的header信息
  • --http2 表示启用http/2,这个参数也可以不带,因为客户端支持的话,会优先使用http/2去链接,服务端不支持的时候降级到http/1.1

curl with –http2

这样就实现了只返回了一个header信息,并且链接没有断开。
我们再通过前边介绍过的h2c来看下请求的效果:

这里有图

可以看到返回的只有一个Header信息,并且是没有END_STREAM标记的。

本次的实践内容到这里就可以结束了,最终实现的代码很简单,但是为什么这样可以实现呢,在缺少相关资料的情况下,很难知道这样做是可以实现该目的的,那么接下来就从Go语言中对HTTP/2的实现来一探究竟吧:

阅读全文

HTTP/2 in GO(二)

相关阅读:

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

HTTP/2新增特性

  • 二进制分帧(HTTP Frames)
  • 多路复用
  • 头部压缩
  • 服务端推送(Server Push)

头部压缩

在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 编码 了。

阅读全文

HTTP/2 in GO(一)

最近由于做一些相关项目,需要使用到HTTP/2的一些特性,花了两天的时间看了下HTTP/2的RFC-7540 的文档,又花了一天时间看了下go语言中http server中对HTTP/2的实现,做一些笔记,记录一些心得。内容比较多,会分多篇写,具体是几篇,看情况定吧。

HTTP/2 RFC7540

先来看一下什么是HTTP/2,为什么不是HTTP/1.2?HTTP/2 没有改动 HTTP 的应用语义。HTTP 方法、状态代码、URI 等概念都跟HTTP/1.1一样,但是HTTP/2在数据传输过程中做了二进制分帧(frame)处理,这点跟之前不一样,通过分帧,HTTP/2对我们的应用隐藏了其复杂性,达到了既能支持一些新特性,又能兼容之前的所有应用。所以,如果我们是跟之前一样,做一些普通的web应用,对HTTP/2的使用跟HTTP/1没有任何区别。但如果我们希望能利用到HTTP/2的一些新特性,就需要对它有一些更深入的了解。

HTTP/2新增特性

  • 二进制分帧(HTTP Frames)
  • 多路复用
  • 头部压缩
  • 服务端推送(server push)

二进制分帧(HTTP Frames)

HTTP/2最革命性的原因就在于这个二进制分帧了,要了解二进制分帧在客户端和服务端传输的过程,需要了解三个概念:

  • Frame,帧,HTTP/2协议里通信的最小单位,每个帧有自己的格式,不同类型的帧负责传输不同的消息
  • Message, 消息,类似Request/Response消息,每个消息包含一个或多个帧
  • Stream,流,建立链接后的一个双向字节流,用来传输消息,每次传输的是一个或多个帧

HTTP/2里边,这些概念的关系是这样的:

  • 所有的通信都在一个tcp链接上完成,会建立一个或多个stream来传递数据
  • 每个stream都有唯一的id标识和一些优先级信息,客户端发起的stream的id为单数,服务端发起的stream id为偶数
  • 每个message就是一次Request或Response消息,包含一个或多个帧,比如只返回header帧,相当于HTTP里HEAD method请求的返回;或者同时返回header和Data帧,就是正常的Response响应。
  • Frame是最小的通信单位,承载着特定类型的数据,例如 Headers, Data, Ping, Setting等等。 来自不同stream的frame可以交错发送,然后再根据每个Frame的header中的数据流标识符重新组装。

简言之,HTTP/2 将 HTTP 协议通信分解为二进制编码Frame的交换,这些Frame对应着特定Stream中的Message。所有这些都在一个 TCP 连接内复用。这是 HTTP/2 协议所有其他功能和性能优化的基础。

阅读全文

作者的图片

DigDeeply

Technology Stack: Golang/PHP/Openresty, and so on…

Web Development Engineer

Beijing China