Dev Life
Ejemplo de cliente HTTP/2 Cleartext (H2C) en Go
Este artículo del blog fue escrito y autopublicado por un miembro de nuestro equipo de desarrollo. También puedes encontrarlo en Mediano.
Dado que mis habilidades de búsqueda en internet me fallaron y el único ejemplo funcional de un cliente H2C que pude encontrar estaba en la propia suite de pruebas de go, aquí voy a exponer lo que descubrí sobre la asistencia para H2C en golang.
En primer lugar, por si te preguntas qué es H2C, básicamente es HTTP/2 pero sin TLS. Es comprensible que H2C no se promocione de forma generalizada, ya que HTTP/2 con TLS es más seguro y no es vulnerable a ciertos tipos de ataques. Sin embargo, H2C tiene sus casos de uso; por ejemplo, GRPC utiliza H2C cuando creas un cliente con grpc.WithInsecure().
En segundo lugar, el código estándar de golang es compatible con HTTP2, pero no admite H2C directamente. La asistencia para H2C solo existe en el paquete golang.org/x/net/http2/h2c. Puedes hacer que tu servidor HTTP sea compatible con H2C si envuelves tu manejador o mux con h2c.NewHandler() de la siguiente manera.
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")
El código anterior permite que el servidor admita la actualización a H2C y el conocimiento previo de H2C junto con los protocolos estándar HTTP/2 y HTTP/1.1 que golang admite de forma nativa.
Si no te interesa admitir HTTP/1.1, puedes ejecutar este código, que solo admite el conocimiento previo de 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)
}),
})
}
Una vez que tengas un servidor en ejecución, puedes probarlo instalando curl-OpenSSL y usando curl para probar tu servidor habilitado para H2C.
$ brew install curl-openssl
# Add curl-openssl to the front of your PATH
$ export PATH="/usr/local/opt/curl-openssl/bin:$PATH"
Conexión a través de HTTP1.1 y luego actualización a 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
Conexión a través de 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
Ahora bien, ¿recuerdas que dije que la biblioteca estándar de golang no admite H2C? Aunque eso es técnicamente correcto, hay una solución alternativa para lograr que el cliente estándar HTTP/2 de golang se conecte a un servidor habilitado para H2C.
Para ello, tienes que sobrescribir DialTLS y establecer el indicador supersecreto 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)
Aunque todo esto parezca un poco extraño, la verdad es que funciona muy bien y tiene un buen rendimiento en entornos de producción.
Hay un ejemplo completo y funcional disponible aquí http://github.com/thrawn01/h2c-golang-example.