生命不止,继续 go go go !!!
继续echo web框架,今天搞一下http2。
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功能。
生成证书
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
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"))
}
浏览器输入:
https://localhost:1323/request
结果:
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
golang.org/x/net/http2
文档地址:
https://godoc.org/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"
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)
})
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)
}
浏览器输入:
https://localhost:8080/
结果:
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,*
Server Push
Server Push是什么
简单来讲就是当用户的浏览器和服务器在建立链接后,服务器主动将一些资源推送给浏览器并缓存起来,这样当浏览器接下来请求这些资源时就直接从缓存中读取,不会在从服务器上拉了,提升了速率。举一个例子就是:
假如一个页面有3个资源文件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相关的帧主要分成这几种类型:
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。
了解了相关的帧类型,下面就是具体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的资源后会缓存起来当请求这个资源时会从直接从从缓存中读取。
Golang1.8中的Server Push
代码main.go:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
var image []byte
func init() {
var err error
image, err = ioutil.ReadFile("./image.png")
if err != nil {
panic(err)
}
}
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>`)
}
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)
}
浏览器输入:
https://localhost:18443/
可以使用插件HTTP/2 and SPDY indicator
chrome://net-internals/#http2
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"))
}
浏览器输入:
https://localhost:1323/
参考:
http://www.alloyteam.com/2017/01/http2-server-push-research/
![这里写图片描述]()
作者:wangshubo1989 发表于2017/11/22 14:07:00
原文链接