Home > Article > Backend Development > About the http/2 server function of Go language and how to use the client
The following column golang tutorial will introduce you to the http/2 server function and client use of Go language. I hope it will be helpful to friends in need!
Preface
As we all know, Go’s standard library HTTP server supports HTTP/2 by default. Well, in this article, we will first showcase Go's http/2 server capabilities and explain how to use them as clients.
In this article, we will first showcase Go’s http/2 server capabilities and explain how to use them as clients. Go's standard library HTTP server supports HTTP/2 by default.
Not much to say below, let’s take a look at the detailed introduction
HTTP/2 server
First of all , let’s create an http/2 server in Go! According to the http/2 documentation, everything is automatically configured for us and we don't even need to import Go's standard library http2 package:
HTTP/2 forces the use of TLS. In order to achieve this, we first need a private key and a certificate. On Linux, the following command performs this task.
openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt
This command will generate two files: server.key and server.crt
Now, for the server code, in its simplest form, we Will use Go's standard library HTTP server and enable TLS with the generated SSL file.
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 Client
In go, the standard http.Client is also used for http/2 requests. The only difference is that in the Client's Transport field, http2.Transport is used instead of http.Transport.
The server certificate we generated is "self-signed," meaning it is not signed by a known Certificate Authority (CA). This will cause our client to not believe it:
package main import ( "fmt" "net/http" ) const url = "https://localhost:8000" func main() { _, err := http.Get(url) fmt.Println(err) }
Let's try running it:
$ go run h2-client.go Get https://localhost:8000: x509: certificate signed by unknown authority
In the server log we will also see that the client (remote) has an error :
http: TLS handshake error from [::1]:58228: remote error: tls: bad certificate
To solve this problem, we can Configure our client with a custom TLS configuration. We will add the server certificate file to the client "certificate pool" because we trust it even though it is not signed by a known CA.
We will also add an option to choose between HTTP/1.1 and HTTP/2 transports based on command line flags.
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) ) }
This time we get the correct response:
$ go run h2-client.go Got response 200: HTTP/2.0 Hello
In the server log we will see the correct log line: Got connection: HTTP/2.0!!
But what happens when we try to use HTTP/1.1 transport?
$ go run h2-client.go -version 1 Got response 200: HTTP/1.1 Hello
Our server doesn't have anything specific to HTTP/2, so it supports HTTP/1.1 connections. This is important for backward compatibility. Additionally, the server log indicates that the connection is HTTP/1.1: Got connection: HTTP/1.1.
HTTP/2 Advanced Features
We have created an HTTP/2 client-server connection and we are enjoying a secure and efficient connection benefits. But HTTP/2 offers many more features, let's explore them!
Server Push
HTTP/2 allows server push to "construct a synthetic request using the given target".
This can be easily implemented in the server handler (view on 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")) }
Using Server Push
Let's re-run the server, and test the client.
For HTTP/1.1 clients:
$ go run ./h2-client.go -version 1 Got response 200: HTTP/1.1 Hello
The server log will show:
Got connection: HTTP/1.1Handling 1st
Can't push to client
HTTP/1.1 client transport connection generates an http.ResponseWriter which does not implement http.Pusher, which makes sense. In our server code we can choose what to do in this client situation.
For HTTP/2 clients:
go run ./h2-client.go -version 2 Got response 200: HTTP/2.0 Hello
The server log will show:
Got connection: HTTP/2.0Handling 1st
Failed push: feature not supported
This is weird. Our HTTP/2 transport client only got the first "Hello" response. The logs indicate that the connection implements the http.Pusher interface - but once we actually call the Push() function - it fails.
The investigation found that the HTTP/2 client transmission set an HTTP/2 setting flag, indicating that push was disabled.
Therefore, there is currently no option to use the Go client to use server push.
As a side note, Google Chrome as a client can handle server push.
The server log will show what we expect, that the handler is called twice, for paths / and /2nd, even though the client actually Only for path/:
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的全双工通信。以上就是本篇的内容,大家有什么疑问可以在文章下面留言沟通。
The above is the detailed content of About the http/2 server function of Go language and how to use the client. For more information, please follow other related articles on the PHP Chinese website!