Dev Life

Exemple de client HTTP/2 en clair (H2C) en Go

Découvrez ce que nous avons appris sur le support de H2C dans golang et comment configurer le client http2 standard de golang pour se connecter à un serveur compatible H2C. En savoir plus...
Image pour Exemple de client HTTP/2 en clair (H2C) en Go

L’un de nos développeurs a rédigé et auto-publié cet article de blog. Vous pouvez également le retrouver sur Moyen.

Mes recherches sur Internet n’ayant rien donné, et le seul exemple fonctionnel de client H2C que j’ai pu trouver se trouvant dans la suite de tests de Go, je vais vous expliquer ici ce que j’ai découvert sur le support de H2C dans golang.

Tout d’abord, si vous vous demandez ce qu’est H2C, il s’agit essentiellement de HTTP/2 mais sans TLS. H2C n’est naturellement pas très médiatisé, car HTTP/2 avec TLS est plus sécurisé et n’est pas vulnérable à certains types d’attaques. Cependant, H2C n’est pas sans utilité ; par exemple, GRPC utilise H2C lorsque vous créez un client avec grpc.WithInsecure().

Ensuite, il faut savoir que le code golang standard prend en charge HTTP2, mais ne prend pas directement en charge H2C. Le support de H2C n’existe que dans le package golang.org/x/net/http2/h2c . Vous pouvez rendre votre serveur HTTP compatible avec H2C en enveloppant votre gestionnaire ou mux avec h2c.NewHandler() comme ceci.

                                

                                    h2s := &http2.Server{}

handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %v, http: %v", r.URL.Path, r.TLS == nil)
})

server := &http.Server{
    Addr:    "0.0.0.0:1010",
    Handler: h2c.NewHandler(handler, h2s),
}

fmt.Printf("Listening [0.0.0.0:1010]... ")
checkErr(server.ListenAndServe(), "while listening")

                                
                            

Le code ci-dessus permet au serveur de prendre en charge la mise à niveau H2C et la connaissance préalable H2C, ainsi que les standards HTTP/2 et HTTP/1.1 que golang prend en charge nativement.

Si la prise en charge de HTTP/1.1 ne vous intéresse pas, vous pouvez exécuter ce code, qui ne prend en charge que la connaissance préalable H2C.

                                

                                    server := http2.Server{}

l, err := net.Listen("tcp", "0.0.0.0:1010")
checkErr(err, "while listening")

fmt.Printf("Listening [0.0.0.0:1010]... ")

for {
    conn, err := l.Accept()
    checkErr(err, "during accept")

    server.ServeConn(conn, &http2.ServeConnOpts{
        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintf(w, "Hello, %v, http: %v", r.URL.Path, r.TLS == nil)
        }),
    })
}

                                
                            

Une fois que vous avez un serveur en cours d’exécution, vous pouvez le tester en installant curl-OpenSSL et en utilisant curl pour tester votre serveur compatible H2C.

                                

                                    $ brew install curl-openssl

# Add curl-openssl to the front of your PATH
$ export PATH="/usr/local/opt/curl-openssl/bin:$PATH"

                                
                            

Se connecter via HTTP1.1 puis mettre à niveau vers HTTP/2 (H2C)

                                

                                    $ curl -v --http2 http://localhost:1010

* Trying ::1:1010...
* TCP_NODELAY set
* Connected to localhost (::1) port 1010 (#0)

> GET / HTTP/1.1
> Host: localhost:1010
> User-Agent: curl/7.65.0
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA
>
* Mark bundle as not supporting multiuse

< HTTP/1.1 101 Switching Protocols
< Connection: Upgrade
< Upgrade: h2c
* Received 101
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!

< HTTP/2 200
< content-type: text/plain; charset=utf-8
< content-length: 20
< date: Wed, 05 Jun 2019 19:01:40 GMT
<
Hello, /, http: true

                                
                            

Se connecter via HTTP/2 (H2C)

                                

                                    curl -v --http2-prior-knowledge http://localhost:1010

* Trying ::1:1010...
* TCP_NODELAY set
* Connected to localhost (::1) port 1010 (#0)
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fdab8007000)

> GET / HTTP/2
> Host: localhost:1010
> User-Agent: curl/7.65.0
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!

< HTTP/2 200
< content-type: text/plain; charset=utf-8
< content-length: 20
< date: Wed, 05 Jun 2019 19:00:43 GMT
<
Hello, /, http: true

                                
                            

Maintenant, vous souvenez-vous quand j’ai dit que la bibliothèque standard de golang ne prend pas en charge H2C ? Bien que cela soit techniquement exact, il existe une solution de contournement pour que le client HTTP/2 standard de golang se connecte à un serveur compatible H2C.

Pour ce faire, vous devez surcharger DialTLS et définir l’indicateur super secret AllowHTTP.

                                

                                    client := http.Client{
    Transport: &http2.Transport{
        // So http2.Transport doesn't complain the URL scheme isn't 'https'
        AllowHTTP: true,

        // Pretend we are dialing a TLS endpoint.
        // Note: we ignore the passed tls.Config
        DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
            return net.Dial(network, addr)
        },
    },
}

resp, _ := client.Get(url)
fmt.Printf("Client Proto: %d\n", resp.ProtoMajor)

                                
                            

Bien que tout cela paraisse un peu bancal, cela fonctionne en réalité très bien et offre de bonnes performances dans les environnements de production.

Un exemple complet et fonctionnel est disponible ici http://github.com/thrawn01/h2c-golang-example.