這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
研究了一下 golang 的http2 用法
這裡先普及幾個概念
- h2,基於TLS之上構建的HTTP/2,作為ALPN的標識符,兩個位元組表示,0x68, 0x32,即https
- h2c,直接在TCP之上構建的HTTP/2,缺乏安全保證,即http
在HTTP/2 RFC文檔出現之前,以上版本欄位需要添加上草案版本號碼,類似於h2-11,h2c-17
首先寫了一個伺服器的代碼
import ( "fmt" "html" "net/http" "golang.org/x/net/http2")func main() { var server http.Server http2.VerboseLogs = true server.Addr = ":8080" http2.ConfigureServer(&server, &http2.Server{}) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "URL: %q\n", html.EscapeString(r.URL.Path)) ShowRequestInfoHandler(w, r) }) server.ListenAndServe() //不啟用 https 則預設只支援http1.x //log.Fatal(server.ListenAndServeTLS("localhost.cert", "localhost.key"))}func ShowRequestInfoHandler(w http.ResponseWriter, r *http.Request) { // fmt.Fprintf(w, "======") // return 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 就使用了 server.ListenAndServe(),沒想到這裡有個坑,等下介紹
既然服務端使用了http 非 tls 那麼 用戶端就使用 非 tls 了,看代碼
package mainimport ( "crypto/tls" "fmt" "io/ioutil" "log" "net" "net/http" "golang.org/x/net/http2")func main() { url := "http://localhost:8080/" client(url)}func client(url string) { log.SetFlags(log.Llongfile) tr := &http2.Transport{ //可惜服務端 退化成了 http1.x AllowHTTP: true, //充許非加密的連結 // TLSClientConfig: &tls.Config{ // InsecureSkipVerify: true, // }, DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) { return net.Dial(netw, addr) }, } httpClient := http.Client{Transport: tr} resp, err := httpClient.Get(url) if err != nil { log.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { fmt.Println("resp StatusCode:", resp.StatusCode) return } body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println("resp.Body:\n", string(body))}
由於http2 client 沒有暴露 h2c 模式的,所以就 搞了個
AllowHTTP: true, //充許非加密的連結DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) { return net.Dial(netw, addr) },
本意是想這樣實現用戶端h2c
搞完了就運行伺服器,執行用戶端,結果列印
Get http://localhost:8080/: unexpected EOF
看的雲裡霧裡,再抓包一看
粘貼圖片.png
伺服器向用戶端發了一個http1.1 的包,並且還close 了client 連結,為什麼會這樣呢
server.ListenAndServe() //不啟用 https 則預設只支援http1.x
1.png
既然伺服器只支援http1 了那麼用戶端 發http2的請求,伺服器當然要close 連結了。
那麼有沒有辦法解決呢,即伺服器和用戶端都使用 h2c ,用戶端的比較好辦 AllowHTTP: true 充許非加密的連結 並且
DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) { return net.Dial(netw, addr) },
服務端考慮使用更低一層的庫http2庫實現,主要是使用ServCon直接替換掉net/http/中的serv函數,例如
import ( "fmt" "golang.org/x/net/http2" "net/http" "net" "time")//net/http包預設可以採用http2進行服務,在沒有進行https的服務上開啟H2,//需要修改ListenAndServer的預設h2服務type serverHandler struct {}func (sh *serverHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { fmt.Println(req) w.Header().Set("server", "h2test") w.Write([]byte("this is a http2 test sever"))}func main() { server := &http.Server{ Addr: ":8080", Handler: &serverHandler{}, ReadTimeout: 5 * time.Second, WriteTimeout: 5 * time.Second, } //http2.Server.ServeConn() s2 := &http2.Server{ IdleTimeout: 1 * time.Minute, } http2.ConfigureServer(server, s2) l, _ := net.Listen("tcp", ":8080") defer l.Close() fmt.Println("Start server...") for { rwc, err := l.Accept() if err != nil { fmt.Println("accept err:", err) continue } go s2.ServeConn(rwc, &http2.ServeConnOpts{BaseConfig: server}) } //http.ListenAndServe(":8888",&serverHandler{})}