HTTP2 Server Push的研究
2017/01/05 · 基础技术 · HTTP/2
原文出处: AlloyTeam
本文首发地址为-iOS HTTP/2 Server Push 探索 | 李剑飞的博客
生命不止,继续 go go go !!!
1,HTTP2的新特性。
关于HTTP2的新特性,读着可以参看我之前的文章,这里就不在多说了,本篇文章主要讲一下server push这个特性。
HTTP,HTTP2.0,SPDY,HTTPS你应该知道的一些事
继续echo web框架,今天搞一下http2。
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
fields表,既避免了重复header的传输,又减小了需要传输的大小。
服务端推送(server push)
同SPDY一样,HTTP2.0也具有server push功能。
3,Server Push原理是什么。
要想了解server push原理,首先要理解一些概念。我们知道HTTP2传输的格式并不像HTTP1使用文本来传输,而是启用了二进制帧(Frames)格式来传输,和server push相关的帧主要分成这几种类型:
- HEADERS 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的实现过程了:
- 由多路复用我们可以知道HTTP2中对于同一个域名的请求会使用一条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正常的发送HEADERS frame和DATA frames将index.html的内容返回给client。
- client接收到PUSH_PROMISE frame得知stream 2和stream 3来接收推送资源。
- server拿到index.css和index.js便会发送HEADERS frame和DATA frames将资源发送给client。
- client拿到push的资源后会缓存起来当请求这个资源时会从直接从从缓存中读取。
美高梅娱乐平台登录 ,下图表示了整个流程:
HTTP/2
美高梅平台下载 ,生成证书
go run C:gosrccryptotlsgenerate_cert.go --host localhost
2017/11/22 10:06:58 written cert.pem
2017/11/22 10 :06:58 written key.pem
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. 首先我们使用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: '*/*' }, 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
本文由美高梅平台下载发布于美高梅娱乐平台登录,转载请注明出处:李剑飞的博客,今天搞一下http2
关键词: