Maison  >  Article  >  développement back-end  >  À propos de la fonction serveur http/2 du langage Go et comment utiliser le client

À propos de la fonction serveur http/2 du langage Go et comment utiliser le client

藏色散人
藏色散人avant
2020-10-09 14:14:147472parcourir

La colonne suivante du tutoriel Golang vous présentera la fonction serveur http/2 et l'utilisation client du langage Go. J'espère qu'elle sera utile aux amis dans le besoin !

Préface

Comme nous le savons tous, le serveur HTTP de la bibliothèque standard de Go prend en charge HTTP/2 par défaut. Eh bien, dans cet article, nous présenterons d'abord les capacités du serveur http/2 de Go et expliquerons comment les utiliser en tant que clients.

Dans cet article, nous présenterons d’abord les capacités du serveur http/2 de Go et expliquerons comment les utiliser en tant que clients. Le serveur HTTP de la bibliothèque standard de Go prend en charge HTTP/2 par défaut.

Pas grand chose à dire ci-dessous, jetons un œil à l'introduction détaillée

Serveur HTTP/2

Premier de Voilà, créons un serveur http/2 dans Go! D'après la documentation http/2, tout est automatiquement configuré pour nous et nous n'avons même pas besoin d'importer le package http2 de la bibliothèque standard de Go :

HTTP/2 force l'utilisation de TLS. Pour y parvenir, nous avons d’abord besoin d’une clé privée et d’un certificat. Sous Linux, la commande suivante effectue cette tâche.

openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt

Cette commande générera deux fichiers : server.key et server.crt

Maintenant, pour le code du serveur, dans sa forme la plus simple, nous allons utilisez le serveur HTTP de la bibliothèque standard de Go et activez TLS avec le fichier SSL généré.

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"))
}

Client HTTP/2

En go, le http.Client standard est également utilisé pour les requêtes http/2. La seule différence est que dans le champ Transport du client, http2.Transport est utilisé à la place de http.Transport.

Le certificat de serveur que nous avons généré est « auto-signé », ce qui signifie qu'il n'est pas signé par une autorité de certification (CA) connue. Cela empêchera notre client d'y croire :

package main

import (
 "fmt"
 "net/http"
)

const url = "https://localhost:8000"

func main() {
 _, err := http.Get(url)
 fmt.Println(err)
}

Essayons de l'exécuter :

$ go run h2-client.go 
Get https://localhost:8000: x509: certificate signed by unknown authority

Dans le journal du serveur, nous verrons également que le client (distant) a une erreur :

http : erreur de prise de contact TLS de [::1]:58228 : erreur à distance : tls : mauvais certificat

Pour résoudre ce problème, nous pouvons configurer notre client avec une configuration TLS personnalisée. Nous ajouterons le fichier de certificat du serveur au « pool de certificats » du client puisque nous lui faisons confiance même s'il n'est pas signé par une autorité de certification connue.

Nous ajouterons également une option permettant de choisir entre les transports HTTP/1.1 et HTTP/2 en fonction des indicateurs de ligne de commande.

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)
 )
}

Cette fois, nous obtenons la bonne réponse :

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

Dans le journal du serveur, nous verrons la ligne de journal correcte : Connexion obtenue : HTTP/2.0 !!
Mais que se passe-t-il lorsque nous essayons d'utiliser le transport HTTP/1.1 ?

$ go run h2-client.go -version 1
Got response 200: HTTP/1.1 Hello

Notre serveur n'a rien de spécifique à HTTP/2, il prend donc en charge les connexions HTTP/1.1. Ceci est important pour la compatibilité ascendante. De plus, le journal du serveur indique que la connexion est HTTP/1.1 : Connexion obtenue : HTTP/1.1.

Fonctionnalités avancées HTTP/2

Nous avons créé une connexion client-serveur HTTP/2 et nous bénéficions d'avantages de connexion sécurisés et efficaces . Mais HTTP/2 offre bien plus de fonctionnalités, explorons-les !

Server Push

HTTP/2 permet au server push de "construire une requête synthétique en utilisant la cible donnée".
Cela peut être facilement implémenté dans le gestionnaire de serveur (voir sur 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"))
}

Utilisation du push du serveur

Réexécutons le serveur et testons le client.

Pour les clients HTTP/1.1 :

$ go run ./h2-client.go -version 1
Got response 200: HTTP/1.1 Hello

Le journal du serveur affichera :

Connexion obtenue : HTTP/1.1Gestion en premier
Impossible de pousser vers client

La connexion de transport client HTTP/1.1 produit un http.ResponseWriter qui n'implémente pas http.Pusher, ce qui est logique. Dans notre code serveur, nous pouvons choisir quoi faire dans cette situation client.

Pour les clients HTTP/2 :

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

Le journal du serveur affichera :

Connexion obtenue : HTTP/2.0Gestion en premier
Échec du push : fonctionnalité non pris en charge

C'est bizarre. Notre client de transport HTTP/2 n'a reçu que la première réponse « Bonjour ». Les journaux indiquent que la connexion implémente l'interface http.Pusher - mais une fois que nous appelons réellement la fonction Push() - elle échoue.

Le dépannage a révélé que la transmission du client HTTP/2 définit un indicateur de paramètre HTTP/2, indiquant que le push est désactivé.

Par conséquent, il n'existe actuellement aucune option permettant d'utiliser le client Go pour utiliser le push du serveur.

En remarque, Google Chrome, en tant que client, peut gérer le push du serveur.

Le journal du serveur montrera ce que nous attendons, le gestionnaire est appelé deux fois, pour les chemins / et /2ème, même si le client en fait Uniquement pour le chemin/ :

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的全双工通信。以上就是本篇的内容,大家有什么疑问可以在文章下面留言沟通。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer