以下由golang教學欄位介紹Go語言的http/2伺服器功能及客戶端使用,希望對需要的朋友有幫助!
前言
大家都知道,Go的標準函式庫HTTP伺服器預設支援HTTP/2。那麼,在這篇文章中,我們將首先展示Go的http/2伺服器功能,並解釋如何將它們作為客戶端使用。
在這篇文章中,我們將首先展示Go的http/2伺服器功能,並解釋如何將它們作為客戶端使用。 Go的標準庫HTTP伺服器預設支援HTTP/2。
下面話不多說了,來一起看看詳細的介紹吧
HTTP/2 伺服器
首先,讓我們在Go中建立一個http/2伺服器!根據http/2文檔,所有東西都是為我們自動配置的,我們甚至不需要導入Go的標準庫http2包:
HTTP/2強制使用TLS。為了實現這一點,我們首先需要一個私鑰和一個憑證。在Linux上,下面的指令執行這個任務。
openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt
該指令將產生兩個檔案:server.key 以及server.crt
#現在,對於伺服器程式碼,以最簡單的形式,我們將使用Go的標準庫HTTP伺服器,並啟用TLS與產生的SSL檔案。
package main import ( "log" "net/http" ) func main() { // 在 8000 端口启动服务器 // 确切地说,如何运行HTTP/1.1服务器。 srv := &http.Server{Addr:":8000", Handler: http.HandlerFunc(handle)} // 用TLS启动服务器,因为我们运行的是http/2,它必须是与TLS一起运行。 // 确切地说,如何使用TLS连接运行HTTP/1.1服务器。 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) { // 记录请求协议 log.Printf("Got connection: %s", r.Proto) // 向客户发送一条消息 w.Write([]byte("Hello")) }
HTTP/2 用戶端
#在go中,標準 http.Client 也用於http/2請求。惟一的差異是在客戶端的Transport字段,使用 http2.Transport 取代 http.Transport。
我們產生的伺服器憑證是「自簽署」的,這意味著它不是由一個已知的憑證授權單位(CA)簽署的。這將導致我們的客戶端不相信它:
package main import ( "fmt" "net/http" ) const url = "https://localhost:8000" func main() { _, err := http.Get(url) fmt.Println(err) }
讓我們試著運行它:
$ go run h2-client.go Get https://localhost:8000: x509: certificate signed by unknown authority
在伺服器日誌中,我們還將看到客戶端(遠端)有一個錯誤:
http: TLS handshake error from [::1]:58228: remote error: tls: bad certificate
#為了解決這個問題,我們可以用客製化的TLS配置去設定我們的客戶端。我們將把伺服器憑證檔案加入到客戶端「憑證池」中,因為我們信任它,即使它不是已知CA簽署的。
我們還將新增一個選項,根據命令列標誌在HTTP/1.1和HTTP/2傳輸之間進行選擇。
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) ) }
這次我們得到了正確的回應:
$ go run h2-client.go Got response 200: HTTP/2.0 Hello
在伺服器日誌中,我們將看到正確的日誌線:獲得連接:Got connection: HTTP/2.0!!
但是當我們嘗試使用HTTP/1.1傳輸時,會發生什麼事?
$ go run h2-client.go -version 1 Got response 200: HTTP/1.1 Hello
我們的伺服器對HTTP/2沒有任何特定的東西,所以它支援HTTP/1.1連線。這對於向後相容性很重要。此外,伺服器日誌顯示連線是HTTP/1.1:Got connection: HTTP/1.1。
HTTP/2 高級特性
我們創建了一個HTTP/2客戶機-伺服器連接,並且我們正在享受安全有效的連接帶來的好處。但是HTTP/2提供了更多的特性,讓我們來研究它們!
伺服器推送
HTTP/2允許伺服器推送「使用給定的目標建構一個合成請求」。
這可以很容易地在伺服器處理程序中實現(在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")) }
使用伺服器推送
##讓我們重新運行伺服器,並測試客戶機。 對於HTTP / 1.1客戶端:$ go run ./h2-client.go -version 1 Got response 200: HTTP/1.1 Hello
Got connection: HTTP/1.1Handling 1stHTTP/1.1客戶端傳輸連線產生一個http.ResponseWriter 沒有實作http.Pusher,這是有道理的。在我們的伺服器程式碼中,我們可以選擇在這種客戶機的情況下該做什麼。 對於HTTP/2客戶:Can't push to client
go run ./h2-client.go -version 2 Got response 200: HTTP/2.0 Hello
Got connection: HTTP/2.0Handling 1st這很奇怪。我們的HTTP/2傳輸的客戶端只得到了第一個「Hello」回應。日誌顯示連線實作了 http.Pusher 介面——但是一旦我們實際呼叫 Push() 函數——它就失敗了。 排查發現,HTTP/2客戶端傳輸設定了一個HTTP/2設定標誌,表示推送是停用的。 因此,目前沒有選擇使用Go客戶機來使用伺服器推送。 作為附帶說明,google chrome作為一個客戶端可以處理伺服器推送。 伺服器日誌將顯示我們所期望的,處理程序被呼叫兩次,路徑/ 和/2nd,即使客戶實際上只對路徑/:Failed push: feature not supported
Got connection: HTTP/2.0Handling 1st
Got connection: HTTP/2.0Handling 2nd
全双工通信
Go HTTP/2演示页面有一个echo示例,它演示了服务器和客户机之间的全双工通信。
让我们先用CURL来测试一下:
$ 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,并将一个PUT/ECHO发送给“hello”作为主体。服务器以“HELLO”作为主体返回一个HTTP/2 200响应。但我们在这里没有做任何复杂的事情,它看起来像是一个老式的HTTP/1.1半双工通信,有不同的头部。让我们深入研究这个问题,并研究如何使用HTTP/2全双工功能。
服务器实现
下面是HTTP echo处理程序的简化版本(不使用响应)。它使用 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) }
服务器将从请求正文读取器复制到写入ResponseWriter和 Flush() 的“冲洗写入器”。同样,我们看到了笨拙的类型断言样式实现,冲洗操作将缓冲的数据发送给客户机。
请注意,这是全双工,服务器读取一行,并在一个HTTP处理程序调用中重复写入一行。
GO客户端实现
我试图弄清楚一个启用了HTTP/2的go客户端如何使用这个端点,并发现了这个Github问题。提出了类似于下面的代码。
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) }
总结
Go支持与服务器推送和全双工通信的HTTP/2连接,这也支持HTTP/1.1与标准库的标准TLS服务器的连接——这太不可思议了。对于标准的库HTTP客户端,它不支持服务器推送,但是支持标准库的标准HTTP的全双工通信。以上就是本篇的内容,大家有什么疑问可以在文章下面留言沟通。
以上是關於Go語言的http/2伺服器功能及客戶端使用方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!