HTTP2 Server Push的研究
2017/01/05 · 基本功技术 ·
HTTP/2
原稿出处:
AlloyTeam
本文头阵地址为-iOS HTTP/2 Server Push 探索 |
李剑飞的博客
生命不止,继续 go go go !!!
一分钟预览 HTTP2 特色和抓包分析
2016/09/26 · JavaScript
· HTTP/2
原来的书文出处: 段隆贤
1,HTTP2的新特色。
有关HTTP2的新性格,读着能够参考小编前面包车型大巴稿子,那里就不在多说了,本篇作品首要讲一下server
push那脾性子。
HTTP,HTTP2.0,SPDY,HTTPS你应有清楚的一部分事
接轨echo web框架,前日搞一下http2。
背景
近些年,http互联网请求量日益增加,以下是httparchive总计,从二零一二-11-01到贰零壹肆-09-01的乞请数量和传导大小的倾向图:
方今超越58%份客户端&服务端架构的应用程序,都以用http/1.1老是的,现代浏览器与单个域最加纳阿克拉接数,都在4-四个左右,由上海体育场面Total
Requests数据,如若不用CDN分流,平均有21个左右的串行请求。
HTTP2
是1997年宣布http1.1后的1遍首要的立异,在商谈层面革新了上述难点,收缩财富占用,来,直接感受一下差距:
HTTP/2 is the future of the Web, and it is
here!
那是 Akamai 公司创建的多少个官方的演示,用以注明 HTTP/2 比较于事先的
HTTP/1.1 在性质上的高大进步。 同时呼吁 379 张图纸,从Load time
的对待能够看到 HTTP/2 在进程上的优势。
本文全部源码和抓包文件在github
2,Server Push是什么。
大概来讲正是当用户的浏览器和服务器在建立链接后,服务器主动将部分能源推送给浏览器并缓存起来,那样当浏览器接下去请求这一个能源时就直接从缓存中读取,不会在从服务器上拉了,进步了速率。举二个例证正是:
如果3个页面有一个能源文件index.html,index.css,index.js,当浏览器请求index.html的时候,服务器不仅重回index.html的剧情,同时将index.css和index.js的始末push给浏览器,当浏览器下次伏乞那2七个文件时就足以直接从缓存中读取了。
HTTP2
What is HTTP/2?
HTTP/2 is a replacement for how HTTP is expressed “on the wire.” It is
not a ground-up rewrite of the protocol; HTTP methods, status codes and
semantics are the same, and it should be possible to use the same APIs
as HTTP/1.x (possibly with some small additions) to represent the
protocol.
The focus of the protocol is on performance; specifically, end-user
perceived latency, network and server resource usage. One major goal is
to allow the use of a single connection from browsers to a Web site.
新的二进制格式(Binary Format)
HTTP1.x的分析是依据文本。基于文本协议的格式解析存在后天缺陷,文本的表现情势有四种性,要马到功成健壮性考虑的风貌必然很多,二进制则差异,只认0和1的重组。基于那种考虑HTTP2.0的商业事务分析决定接纳二进制格式,达成方便且健壮。
多路复用(MultiPlexing)
即接二连三共享,即每二个request都是是当做连接共享机制的。二个request对应一个id,那样贰个接连上得以有多个request,每一种连接的request能够随意的搅和在一块儿,接收方能够根据request的
id将request再归属到个别区别的服务端请求里面。多路复用原理图:
header压缩
HTTP2.0使用encoder来压缩供给传输的header大小,通信双方分别cache一份header
田野先生s表,既幸免了再也header的传输,又减小了特需传输的分寸。
服务端推送(server push)
同SPDY一样,HTTP2.0也具有server push功能。
HTTP/2 源自 SPDY/2
SPDY 体系协议由谷歌(谷歌)花费,于 2010 年公然。它的陈设指标是下落 5/10的页面加载时间。当下游人如织资深的网络集团都在团结的网站或 APP 中运用了
SPDY 种类协议(当前风行版本是
SPDY/3.1),因为它对质量的晋升是备受关注的。主流的浏览器(谷歌(谷歌(Google))、火狐、Opera)也都早已经支撑
SPDY,它已经济体改成了工业标准,HTTP Working-Group 最后决定以 SPDY/2
为底蕴,开发 HTTP/2。HTTP/2标准于贰零壹肆年三月以讴歌RDXFC 7540正经发布。
不过,HTTP/2 跟 SPDY 仍有例外的地点,首就算以下两点:
HTTP/2 援救明文 HTTP 传输,而 SPDY 强制行使 HTTPS
HTTP/2 音信头的压缩算法选用 HPACK ,而非 SPDY 采取的 DEFLATE(感激网络朋友
逸风之狐指正)
协和式飞机文书档案请见:rfc7540:HTTP2
3,Server Push原理是何等。
要想精通server
push原理,首先要理解一些定义。大家知晓HTTP2传输的格式并不像HTTP1使用文本来传输,而是启用了二进制帧(Frames)格式来传输,和server
push相关的帧主要分为这几种类型:
- HEADECRUISERS
frame(请求重回头帧):那种帧首要带领的http请求头新闻,和HTTP1的header类似。 - DATA frames(数据帧) :那种帧存放真正的数额content,用来传输。
- PUSH_PROMISE
frame(推送帧):那种帧是由server端发送给client的帧,用来表示server
push的帧,那种帧是落到实处server push的要害帧类型。 - RST_STREAM(撤废推送帧):那种帧表示请求关闭帧,简单讲就是当client不想接受有些财富依然收受timeout时会向发送方发送此帧,和PUSH_PROMISE
frame一起使用时表示拒绝也许关闭server push。
Note:HTTP2.0有关的帧其实包蕴10种帧,便是因为底部数据格式的改动,才为HTTP2.0推动众多的表征,帧的引入不仅有益于收缩数量,也有益数据的安全性和保证传输性。
询问了有关的帧类型,上面正是具体server push的落实进度了:
- 由多路复用大家得以知道HTTP第22中学对于同三个域名的央浼会动用一条tcp链接而用分裂的stream
ID来区分各自的伸手。 - 当client使用stream
1请求index.html时,server符合规律处理index.html的伸手,并得以得知index.html页面还就要会呈请index.css和index.js。 - server使用stream 1发送PUSH_PROMISE
frame给client告诉client作者那边能够运用stream 2来推送index.js和stream
3来推送index.css财富。 - server使用stream 1平常的出殡HEADE福特ExplorerS frame和DATA
frames将index.html的内容再次来到给client。 - client接收到PUSH_golang中利用echo框架中的HTTP,性格和抓包分析。PROMISE frame得知stream 2和stream
3来收取推送资源。 - server得到index.css和index.js便会发送HEADEPAJEROS frame和DATA
frames将财富发送给client。 - client获得push的能源后会缓存起来当呼吁这么些财富时会从第贰手从从缓存中读取。
下图表示了百分百流程:
HTTP/2
变动证书
go run C:\go\src\crypto\tls\generate_cert.go --host localhost
2017/11/22 10:06:58 written cert.pem
2017/11/22 10 :06:58 written key.pem
HTTP2本性大概浏览
4,Server Push怎么用。
既然server
push这么神奇,那么咱们怎么利用啊?怎么设置服务器push哪些文件呢?
先是并不是有所的服务器都补助server
push,nginx近来还不匡助这么些特性,能够在nginx的法定博客上得到印证,不过Apache和nodejs都早已支持了server
push那二个表征,须要验证一些的是server
push这些天性是依据浏览器和服务器的,所以浏览器并不曾提供对应的js
api来让用户直接操作和决定push的情节,所以只可以是因此header消息和server的布署来促成具体的push内容,本文首要以nodejs来证实具体怎么使用server
push这一风味。
预备工作:下载nodejs
http2支撑,本地运转nodejs服务。
1. 第3大家应用nodejs搭建基本的server:
JavaScript
var http2 = require(‘http2’); var url=require(‘url’); var
fs=require(‘fs’); var mine=require(‘./mine’).types; var
path=require(‘path’); var server = http2.createServer({ key:
fs.readFileSync(‘./zs/localhost.key’), cert:
fs.readFileSync(‘./zs/localhost.crt’) }, function(request, response) {
var pathname = url.parse(request.url).pathname; var realPath =
path.join(“my”, pathname); //那里设置自身的文件名称; var
pushArray = []; var ext = path.extname(realPath); ext = ext ?
ext.slice(1) : ‘unknown’; var contentType = mine[ext] ||
“text/plain”; if (fs.existsSync(realPath)) {
response.writeHead(200, { ‘Content-Type’:
contentType });
response.write(fs.readFileSync(realPath,’binary’)); } else
{ response.writeHead(404, { ‘Content-Type’: ‘text/plain’
}); response.write(“This request URL ” + pathname + ” was
not found on this server.”); response.end(); } });
server.listen(443, function() { console.log(‘listen on 443’); });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
var http2 = require(‘http2’);
var url=require(‘url’);
var fs=require(‘fs’);
var mine=require(‘./mine’).types;
var path=require(‘path’);
var server = http2.createServer({
key: fs.readFileSync(‘./zs/localhost.key’),
cert: fs.readFileSync(‘./zs/localhost.crt’)
}, function(request, response) {
var pathname = url.parse(request.url).pathname;
var realPath = path.join("my", pathname); //这里设置自己的文件名称;
var pushArray = [];
var ext = path.extname(realPath);
ext = ext ? ext.slice(1) : ‘unknown’;
var contentType = mine[ext] || "text/plain";
if (fs.existsSync(realPath)) {
response.writeHead(200, {
‘Content-Type’: contentType
});
response.write(fs.readFileSync(realPath,’binary’));
} else {
response.writeHead(404, {
‘Content-Type’: ‘text/plain’
});
response.write("This request URL " + pathname + " was not found on this server.");
response.end();
}
});
server.listen(443, function() {
console.log(‘listen on 443’);
});
|
这几行代码便是简单搭建二个nodejs
http2服务,打开chrome,我们能够观看全体请求都走了http2,同时也可以证实多路复用的天性。
此处须求注意几点:
- 创造http2的nodejs服务必须时依据https的,因为后天主流的浏览器都要匡助SSL/TLS的http2,证书和私钥可以团结通过OPENSSL生成。
-
node http2的相关api和符合规律的node httpserver相同,能够平素利用。
-
设置大家的server push:
JavaScript
var pushItem = response.push(‘/css/bootstrap.min.css’, { request:
{ accept: ‘*golang中利用echo框架中的HTTP,性格和抓包分析。/\*’ }, response: {
‘content-type’: ‘text/css’ } });
pushItem.end(fs.readFileSync(‘/css/bootstrap.min.css’,’binary’));
1
2
3
4
5
6
7
8
9
|
var pushItem = response.push(‘/css/bootstrap.min.css’, {
request: {
accept: ‘*/\*’
},
response: {
‘content-type’: ‘text/css’
}
});
pushItem.end(fs.readFileSync(‘/css/bootstrap.min.css’,’binary’));
|
咱俩设置了bootstrap.min.css来通过server
push到大家的浏览器,大家得以在浏览器中查看:
可以见见,运转server push的财富timelime不慢,大大加速了css的取得时间。
那里须要留意下边几点:
- 大家调用response.push(),正是相当于server发起了PUSH_PROMISE
frame来报告浏览器bootstrap.min.css将会由server push来赢得。 - response.push()重返的目的时一个例行的ServerResponse,end(),writeHeader()等办法都足以不奇怪调用。
- 此处一旦针对有些能源调用response.push()即发起PUSH_PROMISE
frame后,要加强容错机制,因为浏览器在下次呼吁那个能源时会且只会等待那几个server
push回来的财富,那里要搞好超时和容错即上面包车型大巴代码: -
JavaScript
try {
pushItem.end(fs.readFileSync(‘my/css/bootstrap.min.css’,’binary’));
} catch(e) { response.writeHead(404, {
‘Content-Type’: ‘text/plain’ }); response.end(‘request
error’); } pushItem.stream.on(‘error’, function(err){
response.end(err.message); }); pushItem.stream.on(‘finish’,
function(err){ console.log(‘finish’); });12345678910111213141516try {pushItem.end(fs.readFileSync(‘my/css/bootstrap.min.css’,’binary’));} catch(e) {response.writeHead(404, {‘Content-Type’: ‘text/plain’});response.end(‘request error’);}pushItem.stream.on(‘error’, function(err){response.end(err.message);});pushItem.stream.on(‘finish’, function(err){console.log(‘finish’);});下边包车型大巴代码你只怕会发现众多和常规nodejs的httpserver不平等的事物,那正是stream,其实任何http2都以以stream为单位,那里的stream其实能够知晓成二个呼吁,越来越多的api能够参考:node-http2。
-
最终给大家推荐一个老外写的专门服务http2的node
server有兴趣的能够尝试一下。
HTTP/2 Server Push 是什么
当用户的浏览器和服务器在成立链接后,服务器主动将部分财富推送给浏览器并缓存起来,那样当浏览器接下去请求这几个财富时就直接从缓存中读取,不会在从服务器上拉了,进步了速率。举二个例证正是:
假诺3个页面有叁个财富文件index.html,index.css,index.js,当浏览器请求index.html的时候,服务器不仅返回index.html的剧情,同时将index.css和index.js的始末push给浏览器,当浏览器下次乞请那2五个文件时就能够平昔从缓存中读取了。
一般来说图所示:
Apple-http2ServerPush
echo中的HTTP/2
代码main.go:
package main
import (
"fmt"
"net/http"
"github.com/labstack/echo"
)
func main() {
e := echo.New()
e.GET("/request", func(c echo.Context) error {
req := c.Request()
format := `
<code>
Protocol: %s<br>
Host: %s<br>
Remote Address: %s<br>
Method: %s<br>
Path: %s<br>
</code>
`
return c.HTML(http.StatusOK, fmt.Sprintf(format, req.Proto, req.Host, req.RemoteAddr, req.Method, req.URL.Path))
})
e.Logger.Fatal(e.StartTLS(":1323", "cert.pem", "key.pem"))
}
浏览器输入:
结果:
Protocol: HTTP/2.0
Host: localhost:1323
Remote Address: [::1]:1905
Method: GET
Path: /request
若果出现谬误:
http: TLS handshake error from [::1]:1735: tls: first record does not
look like a TLS handshake.
请检查是或不是输入的是https
1. 二进制协议
HTTP/2 选用二进制格式传输数据,而非 HTTP/1.x 的文本格式
由上海体育地方能够见见HTTP2在原先的应用层和HTTP层添加了一层二进制传输。
二进制协议的三个功利是,能够定义额外的帧。
HTTP/2
定义了近十种帧(详情可分析抓包文件),为今后的高等级应用打好了基础。借使选用文本完毕那种效益,解析数据将会变得10分劳苦,二进制解析则有利于得多。
RFC7540:Frame Definitions
共谋中定义的帧
5,Server Push相关难点。
- 我们知晓现在大家web的能源一般都以身处CDN上的,那么CDN的优势和server
push的优势有啥差异呢,到底是哪位相比较快吧?这么些难点小编也一贯在商量,本文的有关demo都只好算做1个示范,具体的线上实施还在展开中。 - 由于HTTP2的有个别新特点例如多路复用,server
push等等都以依照同3个域名的,所以这可能会对大家前边对于HTTP1的片段优化措施例如(财富拆分域名,合并等等)不必然适用。 - server
push不仅能够当作拉取静态财富,大家的cgi请求即ajax请求同样能够利用server
push来发送数据。 - 最周到的结果是CDN域名援助HTTP2,web server域名也还要支持HTTP2。
参考资料:
- 皇家赌场手机版,HTTP2官方正规:
- 维基百科:
-
1 赞 1 收藏
评论
HTTP/2 Server Push 原理是怎么
要想掌握server
push原理,首先要通晓一些定义。大家清楚HTTP/2传输的格式并不像HTTP1使用文本来传输,而是启用了二进制帧(Frames)格式来传输,和server
push相关的帧主要分为这二种档次:
- HEADECR-VS
frame(请求重回头帧):那种帧首要教导的http请求头音信,和HTTP1的header类似。 - DATA frames(数据帧) :这种帧存放真正的数额content,用来传输。
- PUSH_PROMISE
frame(推送帧):那种帧是由server端发送给client的帧,用来表示server
push的帧,那种帧是兑现server push的主要帧类型。 - RST_STREAM(撤销推送帧):这种帧表示请求关闭帧,不难讲正是当client不想接受一些财富还是收受timeout时会向发送方发送此帧,和PUSH_PROMISE
frame一起行使时表示拒绝或然关闭server push。
(PS:HTTP/2相关的帧其实蕴涵10种帧,正是因为底部数据格式的变动,才为HTTP/2带来诸多的表征,帧的引入不仅有利于压缩数量,也有益于数据的安全性和可相信传输性。)
打探了相关的帧类型,下边就是现实server push的落到实处进度了:
- 由多路复用大家得以知道HTTP/第22中学对此同贰个域名的请求会利用一条tcp链接而用分裂的stream
ID来分别各自的乞请。 - 当client使用stream
1请求index.html时,server符合规律处理index.html的乞请,并能够识破index.html页面还将要会呈请index.css和index.js。 - server使用stream 1发送PUSH_PROMISE
frame给client告诉client小编那边能够接纳stream 2来推送index.js和stream
3来推送index.css能源。 - server使用stream 1平常的出殡和埋葬HEADE帕杰罗S frame和DATA
frames将index.html的始末重返给client。 - client接收到PUSH_PROMISE frame得知stream 2和stream
3来接过推送能源。 - server拿到index.css和index.js便会发送HEADE福特ExplorerS frame和DATA
frames将能源发送给client。 - client获得push的能源后会缓存起来当呼吁那个能源时会从第贰手从从缓存中读取。
golang.org/x/net/http2
文书档案地址:
获取:
get golang.org/x/net/http2
代码main.go:
package main
import (
"fmt"
"html"
"log"
"net/http"
"golang.org/x/net/http2"
)
func main() {
var srv http.Server
http2.VerboseLogs = true
srv.Addr = ":8080"
// This enables http2 support
http2.ConfigureServer(&srv, nil)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi tester %q\n", html.EscapeString(r.URL.Path))
ShowRequestInfoHandler(w, r)
})
// Listen as https ssl server
// NOTE: WITHOUT SSL IT WONT WORK!!
log.Fatal(srv.ListenAndServeTLS("cert.pem", "key.pem"))
}
func ShowRequestInfoHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "Method: %s\n", r.Method)
fmt.Fprintf(w, "Protocol: %s\n", r.Proto)
fmt.Fprintf(w, "Host: %s\n", r.Host)
fmt.Fprintf(w, "RemoteAddr: %s\n", r.RemoteAddr)
fmt.Fprintf(w, "RequestURI: %q\n", r.RequestURI)
fmt.Fprintf(w, "URL: %#v\n", r.URL)
fmt.Fprintf(w, "Body.ContentLength: %d (-1 means unknown)\n", r.ContentLength)
fmt.Fprintf(w, "Close: %v (relevant for HTTP/1 only)\n", r.Close)
fmt.Fprintf(w, "TLS: %#v\n", r.TLS)
fmt.Fprintf(w, "\nHeaders:\n")
r.Header.Write(w)
}
浏览器输入:
结果:
Hi tester "/"
Method: GET
Protocol: HTTP/2.0
Host: localhost:8080
RemoteAddr: [::1]:2750
RequestURI: "/"
URL: &url.URL{Scheme:"", Opaque:"", User:(*url.Userinfo)(nil), Host:"", Path:"/", RawPath:"", ForceQuery:false, RawQuery:"", Fragment:""}
Body.ContentLength: 0 (-1 means unknown)
Close: false (relevant for HTTP/1 only)
TLS: &tls.ConnectionState{Version:0x303, HandshakeComplete:true, DidResume:false, CipherSuite:0xc02f, NegotiatedProtocol:"h2", NegotiatedProtocolIsMutual:true, ServerName:"localhost", PeerCertificates:[]*x509.Certificate(nil), VerifiedChains:[][]*x509.Certificate(nil), SignedCertificateTimestamps:[][]uint8(nil), OCSPResponse:[]uint8(nil), TLSUnique:[]uint8{0xa6, 0x3c, 0xfe, 0x93, 0x3c, 0x15, 0x4f, 0x74, 0xfc, 0x97, 0xca, 0x73}}
Headers:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Alexatoolbar-Alx_ns_ph: AlexaToolbar/alx-4.0
Cookie: _ga=GA1.1.981224509.1509938615
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36
2. 多路复用
HTTP/2
复用TCP连接,在3个老是里,客户端和浏览器都能够而且发送八个请求或应对,而且并非依据顺序依次对应,那样就制止了”队头堵塞”(见TCP/IP详解卷一)。
各个 Frame Header 都有三个 Stream ID
就是被用来落到实处该天性。每一回请求/响应使用区别的 Stream ID。就好像同多少个 TCP
链接上的数额包通过 IP: PO奥迪Q7T 来分别出多少包去往哪儿一样。
rfc7540: HTTP2
Multiplexing中对Multiplexing的说明
Streams and Multiplexing A “stream” is an independent, bidirectional
sequence of frames exchanged between the client and server within an
HTTP/2 connection. Streams have several important characteristics: o A
single HTTP/2 connection can contain multiple concurrently open streams,
with either endpoint interleaving frames from multiple streams. o
Streams can be established and used unilaterally or shared by either the
client or server. o Streams can be closed by either endpoint. o The
order in which frames are sent on a stream is significant. Recipients
process frames in the order they are received. In particular, the order
of HEADERS and DATA frames is semantically significant. o Streams are
identified by an integer. Stream identifiers are assigned to streams by
the endpoint initiating the stream.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
Streams and Multiplexing
A "stream" is an independent, bidirectional sequence of frames
exchanged between the client and server within an HTTP/2 connection.
Streams have several important characteristics:
o A single HTTP/2 connection can contain multiple concurrently open
streams, with either endpoint interleaving frames from multiple
streams.
o Streams can be established and used unilaterally or shared by
either the client or server.
o Streams can be closed by either endpoint.
o The order in which frames are sent on a stream is significant.
Recipients process frames in the order they are received. In
particular, the order of HEADERS and DATA frames is semantically
significant.
o Streams are identified by an integer. Stream identifiers are
assigned to streams by the endpoint initiating the stream.
|
Server Push 怎么用
Server Push
Server Push是什么
简简单单来讲正是当用户的浏览器和服务器在创制链接后,服务器主动将一部分能源推送给浏览器并缓存起来,那样当浏览器接下去请求那几个财富时就径直从缓存中读取,不会在从服务器上拉了,提高了速率。举一个例子便是:
若果一个页面有一个财富文件index.html,index.css,index.js,当浏览器请求index.html的时候,服务器不仅再次回到index.html的内容,同时将index.css和index.js的剧情push给浏览器,当浏览器下次呼吁那2七个公文时就足以一向从缓存中读取了。
Server Push原理是何许
要想通晓server
push原理,首先要领悟一些定义。大家理解HTTP2传输的格式并不像HTTP1使用文本来传输,而是启用了二进制帧(Frames)格式来传输,和server
push相关的帧首要分为那二种档次:
HEADEQX56S
frame(请求重临头帧):那种帧首要指点的http请求头音信,和HTTP1的header类似。
DATA frames(数据帧) :那种帧存放真正的多少content,用来传输。
PUSH_PROMISE
frame(推送帧):那种帧是由server端发送给client的帧,用来表示server
push的帧,那种帧是兑现server push的重要帧类型。
RST_STREAM(撤废推送帧):那种帧表示请求关闭帧,不难讲就是当client不想接受一些财富照旧收受timeout时会向发送方发送此帧,和PUSH_PROMISE
frame一起行使时表示拒绝或许关闭server push。
打听了有关的帧类型,下边就是具体server push的兑现进程了:
由多路复用大家得以知道HTTP第22中学对于同三个域名的乞请会选用一条tcp链接而用不一样的stream
ID来区别各自的伸手。
当client使用stream
1请求index.html时,server平常处理index.html的请求,并能够得知index.html页面还将要会呈请index.css和index.js。
server使用stream 1发送PUSH_PROMISE
frame给client告诉client笔者那边能够行使stream 2来推送index.js和stream
3来推送index.css能源。
server使用stream 1符合规律的出殡和埋葬HEADEOdysseyS frame和DATA
frames将index.html的始末重回给client。
client接收到PUSH_PROMISE frame得知stream 2和stream 3来收纳推送能源。
server得到index.css和index.js便会发送HEADE福睿斯S frame和DATA
frames将财富发送给client。
client获得push的资源后会缓存起来当呼吁这些能源时会从直接从从缓存中读取。
3. 数据流
数量流发送到3/6的时候,客户端和服务器都得以发送信号(汉兰达ST_STREAM帧),撤废这一个数据流。1.1版废除数据流的绝无仅有办法,即是关门TCP连接。那正是说,HTTP/2
能够撤废某2回呼吁,同时确认保证TCP连接还打开着,能够被别的请求使用。
使用 nghttp2 调试 HTTP/2 流量
翻开 HTTP/2 流量的三种格局
- 在 Chrome 地址栏输入
chrome://net-internals/#http2
,使用 Chrome
自带的 HTTP/2 调节和测试工具;
使用方便,但受限于 Chrome 浏览器,对于 Chrome 不支持的 h2c(HTTP/2
Cleartext,没有配备 TLS 的
HTTP/2)协议无法。同时,那一个工具显示的信息经过了剖析和筛选,不够完善。 - 使用 Wireshark 调试 HTTP/2 流量;
Wireshark 位于服务端和浏览器之间,充当的是个中人角色,用它查看
HTTP/2 over HTTPS
流量时,必须具有网站私钥只怕借助浏览器共享对称密钥,才能解密 TLS
流量,配置起来相比较费劲。
nghttp2,是3个用 C 完毕的 HTTP/2 库,帮忙h2c。它能够做为别的软件的一片段,为其提供 HTTP/2 相关功用(例如 curl 的
HTTP/2 功用正是用的 nghttp2)。除此之外,它还提供了七个有效的 HTTP/2
工具:
- nghttp:HTTP/2 客户端;
- nghttpd:HTTP/2 服务端;
- nghttpx:HTTP/2 代理,提供 HTTP/① 、HTTP/2 等合计时期的更换;
- h2load:HTTP/2 品质测试工具;
Golang1.8中的Server Push
代码main.go:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
var image []byte
// preparing image
func init() {
var err error
image, err = ioutil.ReadFile("./image.png")
if err != nil {
panic(err)
}
}
// Send HTML and push image
func handlerHtml(w http.ResponseWriter, r *http.Request) {
pusher, ok := w.(http.Pusher)
if ok {
fmt.Println("Push /image")
pusher.Push("/image", nil)
}
w.Header().Add("Content-Type", "text/html")
fmt.Fprintf(w, `<html><body><img src="/image"></body></html>`)
}
// Send image as usual HTTP request
func handlerImage(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/png")
w.Write(image)
}
func main() {
http.HandleFunc("/", handlerHtml)
http.HandleFunc("/image", handlerImage)
fmt.Println("start http listening :18443")
err := http.ListenAndServeTLS(":18443", "server.crt", "server.key", nil)
fmt.Println(err)
}
浏览器输入:
能够采取插件HTTP/2 and SPDY indicator
chrome://net-internals/#http2
4. 头消息压缩:
HTTP/2 对音讯头选拔
HPACK
举办削减传输,能够节省音信头占用的网络的流量。而 HTTP/1.x
每一回请求,都会指导多量冗余头新闻,浪费了广大带宽能源。
HTTP2对http头建立索引表,相同的头只发送hash
table 的index, 同时还用了霍夫曼编码和古板的gzip压缩。
nghttp2 安装
先来用 brew 看一下有没有 nghttp 相关的库:
~ brew search nghttp
nghttp2
如上所述是有 nghttp2 的,再用 brew 看下须要安装哪些条件:
~ brew info nghttp2
nghttp2: stable 1.21.0 (bottled), HEAD
HTTP/2 C Library
https://nghttp2.org/
Not installed
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/nghttp2.rb
==> Dependencies
Build: sphinx-doc ✘, pkg-config ✔, cunit ✘
Required: c-ares ✘, libev ✘, openssl ✔, libevent ✘, jansson ✘, boost ✘, spdylay ✘
Recommended: jemalloc ✘
==> Requirements
Optional: python3 ✔
==> Options
--with-examples
Compile and install example programs
--with-python3
Build python3 bindings
--without-docs
Don't build man pages
--without-jemalloc
Build without jemalloc support
--HEAD
Install HEAD version
看来供给的依赖性还挺多。
使用 brew 安装 nghttp2 :
brew install nghttp2
方方面面妥帖后,nghttp2 提供的多少个工具就足以一贯用了。
echo框架中的Server Push
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>HTTP/2 Server Push</title>
<link rel="stylesheet" href="/app.css">
<script src="/app.js"></script>
</head>
<body>
<img class="echo" src="/echo.png">
<h2>The following static files are served via HTTP/2 server push</h2>
<ul>
<li><code>/app.css</code></li>
<li><code>/app.js</code></li>
<li><code>/echo.png</code></li>
</ul>
</body>
</html>
main.go
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main() {
e := echo.New()
e.Static("/", "static")
e.GET("/", func(c echo.Context) (err error) {
pusher, ok := c.Response().Writer.(http.Pusher)
if ok {
if err = pusher.Push("/app.css", nil); err != nil {
return
}
if err = pusher.Push("/app.js", nil); err != nil {
return
}
if err = pusher.Push("/echo.png", nil); err != nil {
return
}
}
return c.File("index.html")
})
e.Logger.Fatal(e.StartTLS(":1323", "cert.pem", "key.pem"))
}
浏览器输入:
参考:
5. 服务器推送
服务端能够更快的把能源推送给客户端。例如服务端能够主动把 JS 和 CSS
文件推送给客户端,而不需求客户端解析 HTML
再发送那几个请求。当客户端须要的时候,它早已在客户端了。
这正是说存在三个题材,假诺客户端设置了缓存如何做。有三种办法(来自社区)
- 客户端能够因而设置SETTINGS_ENABLE_PUSH为0值文告服务器端禁止使用推送
- 察觉缓存后,客户端和服务器都能够发送信号(SportageST_STREAM帧),撤废以此数据流。
-
cache-digest(提案)
rfc7540: HTTP2 Server
Push#### 6. 流优先级
HTTP2允许浏览器钦命财富的预先级。
rfc7540: Stream
Priority
nghttp
nghttp 做为3个功力一体化的 HTTP/2 客户端,十分适合用来查阅和调剂 HTTP/2
流量。它扶助的参数很多,通过法定文书档案可能 nghttp -h
都能查看。最常用多少个参数如下:
- -v, –verbose,输出完整的 debug 消息;
- -n, –null-out,放任下载的数码;
- -a, –get-assets,下载 html 中的 css、js、image 等外链能源;
- -H, –header = < HEADE途观 >,添加请求底部字段,如 -H’:method:
PUT’; - -u, –upgrade,使用 HTTP 的 Upgrade 机制来切磋 HTTP/2 协议,用于
h2c,详见上面包车型客车事例;
以下是运用 nghttp 访问
https://h2o.examp1e.net
的结果。从调节和测试音讯中得以清楚看出 h2c 协商以及 Server Push 的一体进度:
nghttp -nv 'https://h2o.examp1e.net'
[ 0.201] Connected
The negotiated protocol: h2
[ 1.180] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 1.180] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[ 1.180] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[ 1.180] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[ 1.180] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[ 1.180] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
[ 1.180] 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: h2o.examp1e.net
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.21.1
[ 1.373] recv SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):16777216]
[ 1.373] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 1.373] recv (stream_id=13) :method: GET
[ 1.373] recv (stream_id=13) :scheme: https
[ 1.373] recv (stream_id=13) :authority: h2o.examp1e.net
[ 1.373] recv (stream_id=13) :path: /search/jquery-1.9.1.min.js
[ 1.373] recv (stream_id=13) accept: */*
[ 1.373] recv (stream_id=13) accept-encoding: gzip, deflate
[ 1.373] recv (stream_id=13) user-agent: nghttp2/1.21.1
[ 1.373] recv PUSH_PROMISE frame <length=59, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=2)
[ 1.373] recv (stream_id=2) :status: 200
[ 1.373] recv (stream_id=2) server: h2o/2.2.0-beta2
[ 1.373] recv (stream_id=2) date: Mon, 10 Apr 2017 06:30:29 GMT
[ 1.373] recv (stream_id=2) content-type: application/javascript
[ 1.373] recv (stream_id=2) last-modified: Thu, 14 May 2015 04:10:14 GMT
[ 1.373] recv (stream_id=2) etag: "55542026-169d5"
[ 1.373] recv (stream_id=2) accept-ranges: bytes
[ 1.373] recv (stream_id=2) x-http2-push: pushed
[ 1.373] recv (stream_id=2) content-length: 92629
[ 1.373] recv HEADERS frame <length=126, flags=0x04, stream_id=2>
; END_HEADERS
(padlen=0)
; First push response header
[ 1.373] recv (stream_id=13) :method: GET
[ 1.373] recv (stream_id=13) :scheme: https
[ 1.373] recv (stream_id=13) :authority: h2o.examp1e.net
[ 1.373] recv (stream_id=13) :path: /search/oktavia-jquery-ui.js
[ 1.373] recv (stream_id=13) accept: */*
[ 1.373] recv (stream_id=13) accept-encoding: gzip, deflate
[ 1.373] recv (stream_id=13) user-agent: nghttp2/1.21.1
[ 1.373] recv PUSH_PROMISE frame <length=33, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=4)
[ 1.373] recv (stream_id=4) :status: 200
[ 1.373] recv (stream_id=4) server: h2o/2.2.0-beta2
[ 1.373] recv (stream_id=4) date: Mon, 10 Apr 2017 06:30:29 GMT
[ 1.373] recv (stream_id=4) content-type: application/javascript
[ 1.373] recv (stream_id=4) last-modified: Thu, 14 May 2015 04:10:14 GMT
[ 1.373] recv (stream_id=4) etag: "55542026-1388"
[ 1.373] recv (stream_id=4) accept-ranges: bytes
[ 1.374] recv (stream_id=4) x-http2-push: pushed
[ 1.374] recv (stream_id=4) content-length: 5000
[ 1.374] recv HEADERS frame <length=28, flags=0x04, stream_id=4>
; END_HEADERS
(padlen=0)
; First push response header
[ 1.374] recv (stream_id=13) :method: GET
[ 1.374] recv (stream_id=13) :scheme: https
[ 1.374] recv (stream_id=13) :authority: h2o.examp1e.net
[ 1.374] recv (stream_id=13) :path: /search/oktavia-english-search.js
[ 1.374] recv (stream_id=13) accept: */*
[ 1.374] recv (stream_id=13) accept-encoding: gzip, deflate
[ 1.374] recv (stream_id=13) user-agent: nghttp2/1.21.1
[ 1.374] recv PUSH_PROMISE frame <length=35, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=6)
[ 1.374] recv (stream_id=6) :status: 200
[ 1.374] recv (stream_id=6) server: h2o/2.2.0-beta2
[ 1.374] recv (stream_id=6) date: Mon, 10 Apr 2017 06:30:29 GMT
[ 1.374] recv (stream_id=6) content-type: application/javascript
[ 1.374] recv (stream_id=6) last-modified: Thu, 14 May 2015 04:10:14 GMT
[ 1.374] recv (stream_id=6) etag: "55542026-34dd6"
[ 1.374] recv (stream_id=6) accept-ranges: bytes
[ 1.374] recv (stream_id=6) x-http2-push: pushed
[ 1.374] recv (stream_id=6) content-length: 216534
[ 1.374] recv HEADERS frame <length=31, flags=0x04, stream_id=6>
; END_HEADERS
(padlen=0)
; First push response header
[ 1.374] recv (stream_id=13) :method: GET
[ 1.374] recv (stream_id=13) :scheme: https
[ 1.374] recv (stream_id=13) :authority: h2o.examp1e.net
[ 1.374] recv (stream_id=13) :path: /assets/style.css
[ 1.374] recv (stream_id=13) accept: */*
[ 1.374] recv (stream_id=13) accept-encoding: gzip, deflate
[ 1.374] recv (stream_id=13) user-agent: nghttp2/1.21.1
[ 1.374] recv PUSH_PROMISE frame <length=24, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=8)
[ 1.374] recv (stream_id=8) :status: 200
[ 1.374] recv (stream_id=8) server: h2o/2.2.0-beta2
[ 1.374] recv (stream_id=8) date: Mon, 10 Apr 2017 06:30:29 GMT
[ 1.374] recv (stream_id=8) content-type: text/css
[ 1.374] recv (stream_id=8) last-modified: Tue, 20 Sep 2016 05:27:06 GMT
[ 1.374] recv (stream_id=8) etag: "57e0c8aa-1586"
[ 1.374] recv (stream_id=8) accept-ranges: bytes
[ 1.374] recv (stream_id=8) x-http2-push: pushed
[ 1.374] recv (stream_id=8) content-length: 5510
[ 1.374] recv HEADERS frame <length=58, flags=0x04, stream_id=8>
; END_HEADERS
(padlen=0)
; First push response header
[ 1.374] recv (stream_id=13) :method: GET
[ 1.374] recv (stream_id=13) :scheme: https
[ 1.374] recv (stream_id=13) :authority: h2o.examp1e.net
[ 1.374] recv (stream_id=13) :path: /assets/searchstyle.css
[ 1.374] recv (stream_id=13) accept: */*
[ 1.374] recv (stream_id=13) accept-encoding: gzip, deflate
[ 1.374] recv (stream_id=13) user-agent: nghttp2/1.21.1
[ 1.374] recv PUSH_PROMISE frame <length=28, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=10)
[ 1.374] recv (stream_id=10) :status: 200
[ 1.374] recv (stream_id=10) server: h2o/2.2.0-beta2
[ 1.374] recv (stream_id=10) date: Mon, 10 Apr 2017 06:30:29 GMT
[ 1.374] recv (stream_id=10) content-type: text/css
[ 1.374] recv (stream_id=10) last-modified: Tue, 20 Sep 2016 05:27:06 GMT
[ 1.374] recv (stream_id=10) etag: "57e0c8aa-8dd"
[ 1.374] recv (stream_id=10) accept-ranges: bytes
[ 1.374] recv (stream_id=10) x-http2-push: pushed
[ 1.374] recv (stream_id=10) content-length: 2269
[ 1.374] recv HEADERS frame <length=27, flags=0x04, stream_id=10>
; END_HEADERS
(padlen=0)
; First push response header
[ 1.374] recv (stream_id=13) :status: 200
[ 1.374] recv (stream_id=13) server: h2o/2.2.0-beta2
[ 1.374] recv (stream_id=13) date: Mon, 10 Apr 2017 06:30:29 GMT
[ 1.374] recv (stream_id=13) link: </search/jquery-1.9.1.min.js>; rel=preload
[ 1.374] recv (stream_id=13) link: </search/oktavia-jquery-ui.js>; rel=preload
[ 1.374] recv (stream_id=13) link: </search/oktavia-english-search.js>; rel=preload
[ 1.374] recv (stream_id=13) link: </assets/style.css>; rel=preload
[ 1.374] recv (stream_id=13) link: </assets/searchstyle.css>; rel=preload
[ 1.374] recv (stream_id=13) cache-control: no-cache
[ 1.374] recv (stream_id=13) content-type: text/html
[ 1.374] recv (stream_id=13) last-modified: Wed, 05 Apr 2017 06:55:14 GMT
[ 1.374] recv (stream_id=13) etag: "58e494d2-1665"
[ 1.374] recv (stream_id=13) accept-ranges: bytes
[ 1.374] recv (stream_id=13) set-cookie: h2o_casper=AmgAAAAAAAAAAAAYxfEYAAABSA; Path=/; Expires=Tue, 01 Jan 2030 00:00:00 GMT; Secure
[ 1.374] recv (stream_id=13) content-length: 5733
[ 1.374] recv HEADERS frame <length=304, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
[ 1.375] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 1.566] recv DATA frame <length=16137, flags=0x00, stream_id=2>
[ 1.567] recv DATA frame <length=5000, flags=0x01, stream_id=4>
; END_STREAM
[ 1.567] recv DATA frame <length=4915, flags=0x00, stream_id=6>
[ 1.766] recv DATA frame <length=2829, flags=0x00, stream_id=8>
[ 1.766] recv DATA frame <length=2269, flags=0x01, stream_id=10>
; END_STREAM
[ 1.766] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=33120)
[ 1.767] recv DATA frame <length=9065, flags=0x00, stream_id=2>
[ 1.970] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 1.970] recv DATA frame <length=2681, flags=0x01, stream_id=8>
; END_STREAM
[ 1.971] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=2>
(window_size_increment=33855)
[ 1.971] recv DATA frame <length=10072, flags=0x00, stream_id=2>
[ 2.172] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 2.172] recv DATA frame <length=4248, flags=0x00, stream_id=2>
[ 2.173] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 2.173] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=34002)
[ 2.173] recv DATA frame <length=4248, flags=0x00, stream_id=2>
[ 2.577] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 2.578] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 2.579] recv DATA frame <length=12762, flags=0x00, stream_id=6>
[ 2.777] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 2.777] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=6>
(window_size_increment=33241)
[ 2.778] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 3.177] recv DATA frame <length=8505, flags=0x00, stream_id=2>
[ 3.177] recv DATA frame <length=5667, flags=0x00, stream_id=6>
[ 3.177] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=33993)
[ 3.177] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 3.177] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 3.378] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 3.579] recv DATA frame <length=11343, flags=0x00, stream_id=6>
[ 3.580] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=34002)
[ 3.580] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=2>
(window_size_increment=33984)
[ 3.583] recv DATA frame <length=7086, flags=0x00, stream_id=2>
[ 3.779] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 4.186] recv DATA frame <length=7086, flags=0x00, stream_id=2>
[ 4.186] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 4.186] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 4.395] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 4.396] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 4.602] recv DATA frame <length=5667, flags=0x00, stream_id=6>
[ 4.602] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=6>
(window_size_increment=33993)
[ 4.602] recv DATA frame <length=2829, flags=0x00, stream_id=2>
[ 4.602] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=33975)
[ 4.808] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 4.809] recv DATA frame <length=6379, flags=0x01, stream_id=2>
; END_STREAM
[ 5.010] recv DATA frame <length=3536, flags=0x00, stream_id=6>
[ 5.420] recv DATA frame <length=8505, flags=0x00, stream_id=6>
[ 5.420] recv DATA frame <length=5667, flags=0x00, stream_id=6>
[ 5.628] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 5.842] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 5.842] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 5.842] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=34002)
[ 5.842] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=6>
(window_size_increment=33281)
[ 6.057] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 6.273] recv DATA frame <length=8505, flags=0x00, stream_id=6>
[ 6.490] recv DATA frame <length=9924, flags=0x00, stream_id=6>
[ 6.490] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 6.706] recv DATA frame <length=4248, flags=0x00, stream_id=6>
[ 6.706] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=34002)
[ 6.706] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=6>
(window_size_increment=34002)
[ 6.924] recv DATA frame <length=8505, flags=0x00, stream_id=6>
[ 7.141] recv DATA frame <length=8505, flags=0x00, stream_id=6>
[ 7.361] recv DATA frame <length=8505, flags=0x00, stream_id=6>
[ 7.361] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=34020)
[ 7.574] recv DATA frame <length=9924, flags=0x00, stream_id=6>
[ 7.574] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=6>
(window_size_increment=34029)
[ 7.787] recv DATA frame <length=9924, flags=0x00, stream_id=6>
[ 7.787] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 7.998] recv DATA frame <length=7086, flags=0x00, stream_id=6>
[ 8.210] recv DATA frame <length=9924, flags=0x00, stream_id=6>
[ 8.210] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
(window_size_increment=34011)
[ 8.210] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=6>
(window_size_increment=34011)
[ 8.425] recv DATA frame <length=11343, flags=0x00, stream_id=6>
[ 8.426] recv DATA frame <length=2829, flags=0x00, stream_id=6>
[ 8.426] recv DATA frame <length=4053, flags=0x01, stream_id=6>
; END_STREAM
[ 8.631] recv DATA frame <length=4443, flags=0x00, stream_id=13>
[ 8.633] recv DATA frame <length=1290, flags=0x01, stream_id=13>
; END_STREAM
[ 8.633] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=10, error_code=NO_ERROR(0x00), opaque_data(0)=[])
本来,大家也得以采取 grep
搜索出来 server push 的连带 stream:
nghttp -nv 'https://h2o.examp1e.net' | grep 'PUSH_PROMISE'
[ 1.582] recv PUSH_PROMISE frame <length=59, flags=0x04, stream_id=13>
[ 1.582] recv PUSH_PROMISE frame <length=33, flags=0x04, stream_id=13>
[ 1.582] recv PUSH_PROMISE frame <length=35, flags=0x04, stream_id=13>
[ 1.582] recv PUSH_PROMISE frame <length=24, flags=0x04, stream_id=13>
[ 1.582] recv PUSH_PROMISE frame <length=28, flags=0x04, stream_id=13>
浏览器补助
主流浏览器都只帮衬 HTTP/2 Over TLS
使用 NodeJS 搭建 HTTP/2 服务器
在大前端的时期背景下,客户端支付不会点 JavaScript
都快混不下去了,小编前段时间在自家司前端轮岗了两周,再加上此前也写过
ReactNative,但依旧感觉到前端变化之快领人惊叹,革命尚未了结,同志仍需努力啊。
我们直接上代码:
var http2 = require('http2');// http2
var url=require('url'); // https://www.npmjs.com/package/url
var fs=require('fs'); // https://www.npmjs.com/package/fs
var mine=require('mine');
var path=require('path'); // 路径
var server = http2.createServer({
key: fs.readFileSync('./localhost.key'),
cert: fs.readFileSync('./localhost.crt')
}, function(request, response) {
// var pathname = url.parse(request.url).pathname;
var realPath = './push.json' ;//path.join(pathname,"push.json"); //这里设置自己的文件路径,这是该次response返回的内容;
var pushArray = [];
var ext = path.extname(realPath);
ext = ext ? ext.slice(1) : 'unknown';
var contentType = mine[ext] || "text/plain";
if (fs.existsSync(realPath)) {
console.log('success')
response.writeHead(200, {
'Content-Type': contentType
});
response.write(fs.readFileSync(realPath,'binary'));
// 注意 push 路径必须是绝对路径,这是该次 server push 返回的内容
var pushItem = response.push('/Users/f.li/Desktop/http2-nodeServer/newpush.json', {
response: {
'content-type': contentType
}
});
pushItem.end(fs.readFileSync('/Users/f.li/Desktop/http2-nodeServer/newpush.json','binary'),()=>{
console.log('newpush end')
});
response.end();
} else {
response.writeHead(404, {
'Content-Type': 'text/plain'
});
response.write("This request URL " + realPath + " was not found on this server.");
response.end();
}
});
server.listen(3000, function() {
console.log('listen on 3000');
});
那里供给专注几点:
- 创建http2的nodejs服务必须时依照https的,因为明日主流的浏览器都要扶助SSL/TLS的http2,证书和私钥能够协调通过OPENSSL生成。
- node http2的连锁api和例行的node httpserver相同,能够平素使用。
动用 nghttp 测试一下我们的代码有没有进行 server push:
~ nghttp -nv 'https://localhost:3000/'
[ 0.007] Connected
The negotiated protocol: h2
[ 0.029] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>
(niv=0)
[ 0.029] 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.029] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.029] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[ 0.029] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[ 0.029] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[ 0.029] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[ 0.029] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
[ 0.029] send HEADERS frame <length=38, 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: localhost:3000
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.21.1
[ 0.043] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.049] recv (stream_id=13) :status: 200
[ 0.049] recv (stream_id=13) content-type: text/plain
[ 0.049] recv (stream_id=13) date: Tue, 11 Apr 2017 08:34:46 GMT
[ 0.049] recv HEADERS frame <length=34, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
[ 0.049] recv DATA frame <length=35, flags=0x00, stream_id=13>
[ 0.049] recv (stream_id=13) :method: GET
[ 0.049] recv (stream_id=13) :scheme: https
[ 0.050] recv (stream_id=13) :authority: localhost:3000
[ 0.050] recv (stream_id=13) :path: /Users/f.li/Desktop/http2-nodeServer/newpush.json
[ 0.050] recv PUSH_PROMISE frame <length=56, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0, promised_stream_id=2)
[ 0.050] recv DATA frame <length=0, flags=0x01, stream_id=13>
; END_STREAM
[ 0.050] recv (stream_id=2) :status: 200
[ 0.050] recv (stream_id=2) date: Tue, 11 Apr 2017 08:34:46 GMT
[ 0.050] recv HEADERS frame <length=2, flags=0x04, stream_id=2>
; END_HEADERS
(padlen=0)
; First push response header
[ 0.050] recv DATA frame <length=21, flags=0x00, stream_id=2>
[ 0.050] recv DATA frame <length=0, flags=0x01, stream_id=2>
; END_STREAM
[ 0.050] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[])
看到了 PUSH_PROMISE 的帧,表达进行了 server push。
平等也足以运用chrome查看 server push,如下图所示:
chrome 查看 http2 server push
服务端介绍宗旨竣工。上面我们来介绍部分 iOS 客户端对 Server Push 的使用。
node中启用http2
node中能够用spdy模块来运行应用,spdy的api,与https是一致的且主流浏览器只帮忙HTTP/2
Over TLS,需求配置 私钥和证书,本地自签定服务器配置可参照引用6,7。
JavaScript
const express = require(‘express’); const fs = require(‘fs’); const
http2 = require(‘spdy’); const path = require(‘path’); const options = {
key: fs.readFileSync(‘./keys/privatekey.pem’), cert:
fs.readFileSync(‘./keys/certificate.pem’) }; const app = new express();
http2 .createServer(options, app) .listen(8080, ()=>{
console.log(`Server is listening on . You can
open the URL in the browser.`) } ) app.use(“/”,(req,res)=>{
res.send(“hello http2!”); })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
const express = require(‘express’);
const fs = require(‘fs’);
const http2 = require(‘spdy’);
const path = require(‘path’);
const options = {
key: fs.readFileSync(‘./keys/privatekey.pem’),
cert: fs.readFileSync(‘./keys/certificate.pem’)
};
const app = new express();
http2
.createServer(options, app)
.listen(8080, ()=>{
console.log(`Server is listening on https://localhost:8080.
You can open the URL in the browser.`)
}
)
app.use("/",(req,res)=>{
res.send("hello http2!");
})
|
如上,对于已存在的品类只要修改几行代码就足以采纳http2.0了。
请求头和响应头:
证实:新版的Chrome,对不安全的证件(如本地的自签订契约服务)会降级到http1.1,firefox不会出现此难题。
启动server push
JavaScript
app.get(“/”,(req,res)=>{ var stream = res.push(‘/app.js’, {
//服务器推送 status: 200, // optional method: ‘GET’, // optional
request: { accept: ‘*/*’ }, response: { ‘content-type’:
‘application/javascript’ } }) stream.on(‘error’, function() { })
stream.end(‘console.log(“http2 push stream, by Lucien “);’)
res.send(`hello http2! <script
src=”/app.js”></script>`);//express 并没有host static
,这个app.js 来自push })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
app.get("/",(req,res)=>{
var stream = res.push(‘/app.js’, { //服务器推送
status: 200, // optional
method: ‘GET’, // optional
request: {
accept: ‘*/*’
},
response: {
‘content-type’: ‘application/javascript’
}
})
stream.on(‘error’, function() {
})
stream.end(‘console.log("http2 push stream, by Lucien ");’)
res.send(`hello http2!
<script src="/app.js"></script>`);//express 并没有host static ,这个app.js 来自push
})
|
源码在github
响应
iOS 使用 HTTP/2 Server Push
Apple 在那下面做的很好,基本落到实处了客户端无感调用http/2 server
push。可是作者查阅了有个别资料,未来只有iOS 10 协理 http/2。
直接上代码吧:
#import "ViewController.h"
@interface ViewController ()<NSURLSessionDelegate>
@property(nonatomic,strong)NSURLSession *session;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
#pragma mark - Touch
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self urlSession];
}
#pragma mark - 发送请求
- (void)urlSession
{
NSURL *url = [NSURL URLWithString:@"https://localhost:3000"];
//发送HTTPS请求是需要对网络会话设置代理的
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *dataTask = [_session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
// 收到该次请求后,立即请求下次的内容
[self urlSessionPush];
}];
[dataTask resume];
}
- (void)urlSessionPush
{
NSURL *url = [NSURL URLWithString:@"https://localhost:3000/Users/f.li/Desktop/http2-nodeServer/newpush.json"];
NSURLSessionDataTask *dataTask = [_session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
[dataTask resume];
}
#pragma mark - URLSession Delegate
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler
{
// 这里还要设置下 plist 中设置 ATS
if (![challenge.protectionSpace.authenticationMethod isEqualToString:@"NSURLAuthenticationMethodServerTrust"])
{
return;
}
NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
{
NSArray *fetchTypes = @[ @"Unknown", @"Network Load", @"Server Push", @"Local Cache"];
for(NSURLSessionTaskTransactionMetrics *transactionMetrics in [metrics transactionMetrics])
{
NSLog(@"protocol[%@] reuse[%d] fetch:%@ - %@", [transactionMetrics networkProtocolName], [transactionMetrics isReusedConnection], fetchTypes[[transactionMetrics resourceFetchType]], [[transactionMetrics request] URL]);
if([transactionMetrics resourceFetchType] == NSURLSessionTaskMetricsResourceFetchTypeServerPush)
{
NSLog(@"Asset was server pushed");
}
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
各自看下服务端和客户端的Log:
客户端:
Http2ServerPush[2525:274943] protocol[h2] reuse[0] fetch:Network Load - https://localhost:3000/
Http2ServerPush[2525:274943] {"message":" http2.0 server is ok"}
Http2ServerPush[2525:274943] protocol[h2] reuse[1] fetch:Server Push - https://localhost:3000/Users/f.li/Desktop/http2-nodeServer/newpush.json
Http2ServerPush[2525:274943] Asset was server pushed
Http2ServerPush[2525:274943] {"message":"newPush"}
服务端:
http2-nodeServer npm start
> http2-nodeServer@1.0.0 start /Users/f.li/Desktop/http2-nodeServer
> node index.js
listen on 3000
success
newpush end
如上所述确实是客户端发出了一遍呼吁,不过服务端只响应了贰遍(该次响应+ server
push)
抓包分析
能够用chrome
内部自带的工具(chrome://net-internals/)查看http2流量,但那一个包消息量比较少,结构不如大家耳熟能详的Fiddler%E6%9F%A5%E7%9C%8Bhttp2%E6%B5%81%E9%87%8F,%E4%BD%86%E8%BF%99%E4%B8%AA%E5%8C%85%E4%BF%A1%E6%81%AF%E9%87%8F%E6%AF%94%E8%BE%83%E5%B0%91%EF%BC%8C%E7%BB%93%E6%9E%84%E4%B8%8D%E5%A6%82%E6%88%91%E4%BB%AC%E7%86%9F%E6%82%89%E7%9A%84Fiddler)
or Wireshark清晰。
Fiddler是间接当做中间代理,能够看作客户端直接与服务端通讯,能够像浏览器那样直接解密https,直接看到https报文,
只是由于受限于.NET
Framework暂不扶助Http2.
用wireshark直接抓包 https:443端口的流量是那样的:
多少被加密了,协议细节完全看不到。
这里介绍了一种办法拿到私钥解包。
抓包https包时要把代理关了,不然私钥不是同3个,wireshark不能够解包(被这么些坑了两时辰T
T)。
一个包内有三个差别的Steam ID
追踪解密后TCP流能够见到,由于多路复用,种种差异的伸手交替传输不一致的帧,所以流数据是乱的。但在同等帧内数据或然平常的。
本文相关德姆o
- Github:lijianfeigeek
最后
最后,HTTP2有更高的传输速度,更少的能源占用,能够去除种种质量优化tricks(如css
sprite,inline-image.)
转车WEB开发的美好今后T.T
参考文献
- HTTP2 Server Push的研究 |
AlloyTeam - 使用 nghttp2 调试 HTTP/2 流量 | JerryQu
的小站 - objective c – HTTP/2 Server Push in iOS 10 – Stack
Overflow - 使用NSURLSession或者AFN发送HTTPS请求 –
简书
参考资料
- Turn-on HTTP/2 today!
- Hypertext Transfer Protocol Version 2
(HTTP/2) - npm spdy
- npm spdy push
- How to create a self-signed SSL
Certificate - HPACK: Header Compression for
HTTP/2 -
用Node.js创立自签约的HTTPS服务器
1 赞 收藏
评论