Dev Life
Exemplo de cliente HTTP/2 Cleartext (H2C) em Go
Esta postagem do blog foi escrita e publicada de forma independente por um dos nossos desenvolvedores. Você também pode encontrá-la em Medium.
Como minha pesquisa na internet falhou e o único exemplo viável de um cliente H2C que encontrei estava na própria suíte de testes do Go, vou detalhar aqui o que descobri sobre o suporte a H2C no Golang.
Primeiro, caso você esteja se perguntando o que é H2C, ele é essencialmente o HTTP/2 sem o TLS. É compreensível que o H2C não seja amplamente divulgado, já que o HTTP/2 com TLS é mais seguro e não está vulnerável a alguns tipos de ataques. No entanto, o H2C tem seus casos de uso; por exemplo, o GRPC usa o H2C quando você cria um cliente com grpc.WithInsecure().
Segundo, o código padrão do Golang oferece suporte ao HTTP2, mas não oferece suporte direto ao H2C. O suporte a H2C só existe no pacote golang.org/x/net/http2/h2c . Você pode tornar seu servidor HTTP compatível com H2C envolvendo seu handler ou mux com h2c.NewHandler() desta forma.
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")
O código acima permite que o servidor ofereça suporte a upgrade de H2C e a conhecimento prévio de H2C, juntamente com o HTTP/2 e o HTTP/1.1 padrão aos quais o Golang oferece suporte nativo.
Se você não precisa oferecer suporte ao HTTP/1.1, pode executar este código, que oferece suporte apenas a conhecimento prévio 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)
}),
})
}
Depois de ter um servidor em execução, você pode testá-lo instalando o curl-OpenSSL e usando o curl para testar seu 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"
Conectar via HTTP1.1 e depois fazer upgrade para o 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
Conectar 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
Agora, lembra quando eu disse que a biblioteca padrão do Golang não oferece suporte ao H2C? Embora isso seja tecnicamente correto, existe uma solução alternativa para fazer o cliente HTTP/2 padrão do Golang se conectar a um servidor habilitado para H2C.
Para fazer isso, você precisa sobrescrever o DialTLS e definir a flag supersecreta 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)
Embora tudo isso pareça um pouco estranho, na verdade funciona muito bem e tem um ótimo desempenho em ambientes de produção.
Um exemplo completo e funcional está disponível aqui http://github.com/thrawn01/h2c-golang-example.