Heim  >  Artikel  >  Backend-Entwicklung  >  Informationen zur http/2-Serverfunktion der Go-Sprache und zur Verwendung des Clients

Informationen zur http/2-Serverfunktion der Go-Sprache und zur Verwendung des Clients

藏色散人
藏色散人nach vorne
2020-10-09 14:14:147572Durchsuche

Die folgende Golang-Tutorial-Kolumne führt Sie in die http/2-Serverfunktionen und die Client-Nutzung der Go-Sprache ein. Ich hoffe, dass sie Freunden in Not hilfreich sein wird!

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ührung

HTTP/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

Für den Servercode verwenden wir nun in seiner einfachsten Form den HTTP-Server der Standardbibliothek von Go und aktivieren TLS mit der generierten SSL-Datei .

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 authority

Im Serverprotokoll sehen wir auch, dass der Client (Remote) einen Fehler hat:

http: TLS-Handshake-Fehler von [: :1]:58228: Remote-Fehler: tls: schlechtes Zertifikat


Um 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 Hello

Im 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 Hello

Unser 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 Hello

Das Serverprotokoll zeigt:

Verbindung erhalten: HTTP/1.1Handling 1st

Kann nicht an Client übertragen werden

HTTP/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:

go run ./h2-client.go -version 2
Got response 200: HTTP/2.0 Hello

Das Serverprotokoll zeigt:

Verbindung erhalten: HTTP/2.0Handling 1st

Push fehlgeschlagen: Funktion nicht unterstützt

Das 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:

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!

Stellungnahme:
Dieser Artikel ist reproduziert unter:jb51.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen