Heim >Backend-Entwicklung >Golang >Informationen zur http/2-Serverfunktion der Go-Sprache und zur Verwendung des Clients
Vorwort
Wie wir alle wissen, unterstützt der HTTP-Server der Standardbibliothek von Go standardmäßig HTTP/2. Nun, in diesem Artikel werden wir zunächst die http/2-Serverfunktionen von Go vorstellen und erklären, wie man sie als Clients verwendet. In diesem Artikel stellen wir zunächst die http/2-Serverfunktionen von Go vor und erklären, wie man sie als Clients verwendet. Der HTTP-Server der Standardbibliothek von Go unterstützt standardmäßig HTTP/2. Weiter unten gibt es nicht viel zu sagen, werfen wir einen Blick auf die detaillierte EinführungHTTP/2-Server
Erstellen wir zunächst einen http/2-Server in Go! Laut http/2-Dokumentation wird alles automatisch für uns konfiguriert und wir müssen nicht einmal das Standardbibliotheks-http2-Paket von Go importieren: HTTP/2 erzwingt TLS. Um dies zu erreichen, benötigen wir zunächst einen privaten Schlüssel und ein Zertifikat. Unter Linux führt der folgende Befehl diese Aufgabe aus.openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt
Dieser Befehl generiert zwei Dateien: server.key und server.crt
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 wird der Standard-http.Client auch für http/2-Anfragen verwendet. Der einzige Unterschied besteht darin, dass im Transportfeld des Clients http2.Transport anstelle von http.Transport verwendet wird. Das von uns generierte Serverzertifikat ist „selbstsigniert“, d. h. es ist nicht von einer bekannten Zertifizierungsstelle (CA) signiert. Dies führt dazu, dass unser Client es nicht glaubt:package main import ( "fmt" "net/http" ) const url = "https://localhost:8000" func main() { _, err := http.Get(url) fmt.Println(err) }Versuchen wir es auszuführen:
$ go run h2-client.go Get https://localhost:8000: x509: certificate signed by unknown authorityIm Serverprotokoll sehen wir auch, dass der Client (Remote) einen Fehler hat:
http: TLS-Handshake-Fehler von [: :1]:58228: Remote-Fehler: tls: schlechtes ZertifikatUm dieses Problem zu lösen, können wir unseren Client mit einer benutzerdefinierten TLS-Konfiguration konfigurieren. Wir werden die Serverzertifikatdatei zum „Zertifikatpool“ des Clients hinzufügen, da wir ihr vertrauen, auch wenn sie nicht von einer bekannten Zertifizierungsstelle signiert ist. Wir werden außerdem eine Option hinzufügen, mit der Sie basierend auf Befehlszeilen-Flags zwischen HTTP/1.1- und HTTP/2-Transporten wählen können.
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) ) }Dieses Mal erhalten wir die richtige Antwort:
$ go run h2-client.go Got response 200: HTTP/2.0 HelloIm Serverprotokoll sehen wir die richtige Protokollzeile: Verbindung hergestellt: HTTP/2.0!!
Aber was passiert, wenn wir versuchen, HTTP/1.1-Transport zu verwenden?
$ go run h2-client.go -version 1 Got response 200: HTTP/1.1 HelloUnser Server verfügt über keine spezifischen HTTP/2-Funktionen und unterstützt daher HTTP/1.1-Verbindungen. Dies ist wichtig für die Abwärtskompatibilität. Darüber hinaus gibt das Serverprotokoll an, dass die Verbindung HTTP/1.1 ist: Verbindung erhalten: HTTP/1.1.
Erweiterte HTTP/2-Funktionen
Wir haben eine HTTP/2-Client-Server-Verbindung erstellt und genießen die Vorteile einer sicheren und effizienten Verbindung. Aber HTTP/2 bietet noch viel mehr Funktionen, erkunden wir sie!Server Push
HTTP/2 ermöglicht Server Push, „eine synthetische Anfrage unter Verwendung des angegebenen Ziels zu erstellen“.Dies kann einfach im Server-Handler implementiert werden (siehe 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")) }
Mit Server Push
Lassen Sie uns den Server erneut ausführen und den Client testen. Für HTTP/1.1-Clients:$ go run ./h2-client.go -version 1 Got response 200: HTTP/1.1 HelloDas Serverprotokoll zeigt:
Verbindung erhalten: HTTP/1.1Handling 1stHTTP/1.1-Client-Transportverbindung ergibt einen HTTP-ResponseWriter implementiert http.Pusher nicht, was Sinn macht. In unserem Servercode können wir auswählen, was in dieser Client-Situation geschehen soll. Für HTTP/2-Clients:Kann nicht an Client übertragen werden
go run ./h2-client.go -version 2 Got response 200: HTTP/2.0 HelloDas Serverprotokoll zeigt:
Verbindung erhalten: HTTP/2.0Handling 1stDas ist seltsam. Unser HTTP/2-Transportclient erhielt nur die erste „Hallo“-Antwort. Aus den Protokollen geht hervor, dass die Verbindung die http.Pusher-Schnittstelle implementiert – aber sobald wir die Push()-Funktion tatsächlich aufrufen, schlägt sie fehl. Bei der Fehlerbehebung wurde festgestellt, dass die HTTP/2-Clientübertragung ein HTTP/2-Einstellungsflag setzt, was darauf hinweist, dass Push deaktiviert ist. Daher gibt es derzeit keine Möglichkeit, den Go-Client für die Nutzung von Server-Push zu verwenden. Nebenbei bemerkt, Google Chrome als Client kann Server-Push verarbeiten. Das Serverprotokoll zeigt, was wir erwarten, der Handler wird zweimal aufgerufen, für Pfad / und /2nd, obwohl der Client eigentlich nur Anfragen für Pfad / hat:Push fehlgeschlagen: Funktion nicht unterstützt
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的全双工通信。以上就是本篇的内容,大家有什么疑问可以在文章下面留言沟通。
Das obige ist der detaillierte Inhalt vonInformationen zur http/2-Serverfunktion der Go-Sprache und zur Verwendung des Clients. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!