前言
大家都知道,Go的標(biāo)準(zhǔn)庫(kù)HTTP服務(wù)器默認(rèn)支持HTTP/2。那么,在這篇文章中,我們將首先展示Go的http/2服務(wù)器功能,并解釋如何將它們作為客戶端使用。
在這篇文章中,我們將首先展示Go的http/2服務(wù)器功能,并解釋如何將它們作為客戶端使用。Go的標(biāo)準(zhǔn)庫(kù)HTTP服務(wù)器默認(rèn)支持HTTP/2。
下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧
HTTP/2 服務(wù)器
首先,讓我們?cè)贕o中創(chuàng)建一個(gè)http/2服務(wù)器!根據(jù)http/2文檔,所有東西都是為我們自動(dòng)配置的,我們甚至不需要導(dǎo)入Go的標(biāo)準(zhǔn)庫(kù)http2包:
HTTP/2強(qiáng)制使用TLS。為了實(shí)現(xiàn)這一點(diǎn),我們首先需要一個(gè)私鑰和一個(gè)證書(shū)。在Linux上,下面的命令執(zhí)行這個(gè)任務(wù)。
openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt
該命令將生成兩個(gè)文件:server.key 以及 server.crt
現(xiàn)在,對(duì)于服務(wù)器代碼,以最簡(jiǎn)單的形式,我們將使用Go的標(biāo)準(zhǔn)庫(kù)HTTP服務(wù)器,并啟用TLS與生成的SSL文件。
package main
import (
"log"
"net/http"
)
func main() {
// 在 8000 端口啟動(dòng)服務(wù)器
// 確切地說(shuō),如何運(yùn)行HTTP/1.1服務(wù)器。
srv := http.Server{Addr:":8000", Handler: http.HandlerFunc(handle)}
// 用TLS啟動(dòng)服務(wù)器,因?yàn)槲覀冞\(yùn)行的是http/2,它必須是與TLS一起運(yùn)行。
// 確切地說(shuō),如何使用TLS連接運(yùn)行HTTP/1.1服務(wù)器。
log.Printf("Serving on https://0.0.0.0:8000")
log.Fatal(srv.ListenAndServeTLS("server.crt", "server.key"))
}
func handle(w http.ResponseWriter, r *http.Request) {
// 記錄請(qǐng)求協(xié)議
log.Printf("Got connection: %s", r.Proto)
// 向客戶發(fā)送一條消息
w.Write([]byte("Hello"))
}
HTTP/2 客戶端
在go中,標(biāo)準(zhǔn) http.Client 也用于http/2請(qǐng)求。惟一的區(qū)別是在客戶端的Transport字段,使用 http2.Transport 代替 http.Transport。
我們生成的服務(wù)器證書(shū)是“自簽名”的,這意味著它不是由一個(gè)已知的證書(shū)頒發(fā)機(jī)構(gòu)(CA)簽署的。這將導(dǎo)致我們的客戶端不相信它:
package main
import (
"fmt"
"net/http"
)
const url = "https://localhost:8000"
func main() {
_, err := http.Get(url)
fmt.Println(err)
}
讓我們?cè)囍\(yùn)行它:
$ go run h2-client.go
Get https://localhost:8000: x509: certificate signed by unknown authority
在服務(wù)器日志中,我們還將看到客戶端(遠(yuǎn)程)有一個(gè)錯(cuò)誤:
http: TLS handshake error from [::1]:58228: remote error: tls: bad certificate
為了解決這個(gè)問(wèn)題,我們可以用定制的TLS配置去配置我們的客戶端。我們將把服務(wù)器證書(shū)文件添加到客戶端“證書(shū)池”中,因?yàn)槲覀冃湃嗡?,即使它不是由已知CA簽名的。
我們還將添加一個(gè)選項(xiàng),根據(jù)命令行標(biāo)志在HTTP/1.1和HTTP/2傳輸之間進(jìn)行選擇。
package main
import (
"crypto/tls"
"crypto/x509"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"golang.org/x/net/http2"
)
const url = "https://localhost:8000"
var httpVersion = flag.Int("version", 2, "HTTP version")
func main() {
flag.Parse()
client := http.Client{}
// Create a pool with the server certificate since it is not signed
// by a known CA
caCert, err := ioutil.ReadFile("server.crt")
if err != nil {
log.Fatalf("Reading server certificate: %s", err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// Create TLS configuration with the certificate of the server
tlsConfig := tls.Config{
RootCAs: caCertPool,
}
// Use the proper transport in the client
switch *httpVersion {
case 1:
client.Transport = http.Transport{
TLSClientConfig: tlsConfig,
}
case 2:
client.Transport = http2.Transport{
TLSClientConfig: tlsConfig,
}
}
// Perform the request
resp, err := client.Get(url)
if err != nil {
log.Fatalf("Failed get: %s", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Failed reading response body: %s", err)
}
fmt.Printf(
"Got response %d: %s %s\n",
resp.StatusCode, resp.Proto, string(body)
)
}
這一次我們得到了正確的回應(yīng):
$ go run h2-client.go
Got response 200: HTTP/2.0 Hello
在服務(wù)器日志中,我們將看到正確的日志線:獲得連接:Got connection: HTTP/2.0!!
但是當(dāng)我們嘗試使用HTTP/1.1傳輸時(shí),會(huì)發(fā)生什么呢?
$ go run h2-client.go -version 1
Got response 200: HTTP/1.1 Hello
我們的服務(wù)器對(duì)HTTP/2沒(méi)有任何特定的東西,所以它支持HTTP/1.1連接。這對(duì)于向后兼容性很重要。此外,服務(wù)器日志表明連接是HTTP/1.1:Got connection: HTTP/1.1。
HTTP/2 高級(jí)特性
我們創(chuàng)建了一個(gè)HTTP/2客戶機(jī)-服務(wù)器連接,并且我們正在享受安全有效的連接帶來(lái)的好處。但是HTTP/2提供了更多的特性,讓我們來(lái)研究它們!
服務(wù)器推送
HTTP/2允許服務(wù)器推送“使用給定的目標(biāo)構(gòu)造一個(gè)合成請(qǐng)求”。
這可以很容易地在服務(wù)器處理程序中實(shí)現(xiàn)(在github上的視圖):
func handle(w http.ResponseWriter, r *http.Request) {
// Log the request protocol
log.Printf("Got connection: %s", r.Proto)
// Handle 2nd request, must be before push to prevent recursive calls.
// Don't worry - Go protect us from recursive push by panicking.
if r.URL.Path == "/2nd" {
log.Println("Handling 2nd")
w.Write([]byte("Hello Again!"))
return
}
// Handle 1st request
log.Println("Handling 1st")
// Server push must be before response body is being written.
// In order to check if the connection supports push, we should use
// a type-assertion on the response writer.
// If the connection does not support server push, or that the push
// fails we just ignore it - server pushes are only here to improve
// the performance for HTTP/2 clients.
pusher, ok := w.(http.Pusher)
if !ok {
log.Println("Can't push to client")
} else {
err := pusher.Push("/2nd", nil)
if err != nil {
log.Printf("Failed push: %v", err)
}
}
// Send response body
w.Write([]byte("Hello"))
}
使用服務(wù)器推送
讓我們重新運(yùn)行服務(wù)器,并測(cè)試客戶機(jī)。
對(duì)于HTTP / 1.1客戶端:
$ go run ./h2-client.go -version 1
Got response 200: HTTP/1.1 Hello
服務(wù)器日志將顯示:
Got connection: HTTP/1.1Handling 1st
Can't push to client
HTTP/1.1客戶端傳輸連接產(chǎn)生一個(gè) http.ResponseWriter 沒(méi)有實(shí)現(xiàn)http.Pusher,這是有道理的。在我們的服務(wù)器代碼中,我們可以選擇在這種客戶機(jī)的情況下該做什么。
對(duì)于HTTP/2客戶:
go run ./h2-client.go -version 2
Got response 200: HTTP/2.0 Hello
服務(wù)器日志將顯示:
Got connection: HTTP/2.0Handling 1st
Failed push: feature not supported
這很奇怪。我們的HTTP/2傳輸?shù)目蛻舳酥坏玫搅说谝粋€(gè)“Hello”響應(yīng)。日志表明連接實(shí)現(xiàn)了 http.Pusher 接口——但是一旦我們實(shí)際調(diào)用 Push() 函數(shù)——它就失敗了。
排查發(fā)現(xiàn),HTTP/2客戶端傳輸設(shè)置了一個(gè)HTTP/2設(shè)置標(biāo)志,表明推送是禁用的。
因此,目前沒(méi)有選擇使用Go客戶機(jī)來(lái)使用服務(wù)器推送。
作為一個(gè)附帶說(shuō)明,google chrome作為一個(gè)客戶端可以處理服務(wù)器推送。
服務(wù)器日志將顯示我們所期望的,處理程序被調(diào)用兩次,路徑 / 和 /2nd,即使客戶實(shí)際上只對(duì)路徑 /:
Got connection: HTTP/2.0Handling 1st
Got connection: HTTP/2.0Handling 2nd
全雙工通信
Go HTTP/2演示頁(yè)面有一個(gè)echo示例,它演示了服務(wù)器和客戶機(jī)之間的全雙工通信。
讓我們先用CURL來(lái)測(cè)試一下:
$ curl -i -XPUT --http2 https://http2.golang.org/ECHO -d hello
HTTP/2 200
content-type: text/plain; charset=utf-8
date: Tue, 24 Jul 2018 12:20:56 GMT
HELLO
我們把curl配置為使用HTTP/2,并將一個(gè)PUT/ECHO發(fā)送給“hello”作為主體。服務(wù)器以“HELLO”作為主體返回一個(gè)HTTP/2 200響應(yīng)。但我們?cè)谶@里沒(méi)有做任何復(fù)雜的事情,它看起來(lái)像是一個(gè)老式的HTTP/1.1半雙工通信,有不同的頭部。讓我們深入研究這個(gè)問(wèn)題,并研究如何使用HTTP/2全雙工功能。
服務(wù)器實(shí)現(xiàn)
下面是HTTP echo處理程序的簡(jiǎn)化版本(不使用響應(yīng))。它使用 http.Flusher 接口,HTTP/2添加到http.ResponseWriter。
type flushWriter struct {
w io.Writer
}
func (fw flushWriter) Write(p []byte) (n int, err error) {
n, err = fw.w.Write(p)
// Flush - send the buffered written data to the client
if f, ok := fw.w.(http.Flusher); ok {
f.Flush()
}
return
}
func echoCapitalHandler(w http.ResponseWriter, r *http.Request) {
// First flash response headers
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
// Copy from the request body to the response writer and flush
// (send to client)
io.Copy(flushWriter{w: w}, r.Body)
}
服務(wù)器將從請(qǐng)求正文讀取器復(fù)制到寫(xiě)入ResponseWriter和 Flush() 的“沖洗寫(xiě)入器”。同樣,我們看到了笨拙的類(lèi)型斷言樣式實(shí)現(xiàn),沖洗操作將緩沖的數(shù)據(jù)發(fā)送給客戶機(jī)。
請(qǐng)注意,這是全雙工,服務(wù)器讀取一行,并在一個(gè)HTTP處理程序調(diào)用中重復(fù)寫(xiě)入一行。
GO客戶端實(shí)現(xiàn)
我試圖弄清楚一個(gè)啟用了HTTP/2的go客戶端如何使用這個(gè)端點(diǎn),并發(fā)現(xiàn)了這個(gè)Github問(wèn)題。提出了類(lèi)似于下面的代碼。
const url = "https://http2.golang.org/ECHO"
func main() {
// Create a pipe - an object that implements `io.Reader` and `io.Writer`.
// Whatever is written to the writer part will be read by the reader part.
pr, pw := io.Pipe()
// Create an `http.Request` and set its body as the reader part of the
// pipe - after sending the request, whatever will be written to the pipe,
// will be sent as the request body. // This makes the request content dynamic, so we don't need to define it
// before sending the request.
req, err := http.NewRequest(http.MethodPut, url, ioutil.NopCloser(pr))
if err != nil {
log.Fatal(err)
}
// Send the request
resp, err := http.DefaultClient.Do(req) if err != nil {
log.Fatal(err)
}
log.Printf("Got: %d", resp.StatusCode)
// Run a loop which writes every second to the writer part of the pipe
// the current time.
go func() { for {
time.Sleep(1 * time.Second)
fmt.Fprintf(pw, "It is now %v\n", time.Now())
}
}()
// Copy the server's response to stdout.
_, err = io.Copy(os.Stdout, res.Body)
log.Fatal(err)
}
總結(jié)
Go支持與服務(wù)器推送和全雙工通信的HTTP/2連接,這也支持HTTP/1.1與標(biāo)準(zhǔn)庫(kù)的標(biāo)準(zhǔn)TLS服務(wù)器的連接——這太不可思議了。對(duì)于標(biāo)準(zhǔn)的庫(kù)HTTP客戶端,它不支持服務(wù)器推送,但是支持標(biāo)準(zhǔn)庫(kù)的標(biāo)準(zhǔn)HTTP的全雙工通信。以上就是本篇的內(nèi)容,大家有什么疑問(wèn)可以在文章下面留言溝通。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
您可能感興趣的文章:- Go 實(shí)現(xiàn)熱重啟的詳細(xì)介紹
- 詳解如何熱重啟golang服務(wù)器
- 在Go中構(gòu)建并發(fā)TCP服務(wù)器
- 解析Go 標(biāo)準(zhǔn)庫(kù) http.FileServer 實(shí)現(xiàn)靜態(tài)文件服務(wù)
- MongoDB4.0在windows10下的安裝與服務(wù)配置教程詳解
- goland服務(wù)熱重啟的配置文件