HTTP/2 Cleartext (H2C) Client Example in Go

Derrick Wippler
3 min readJun 5, 2019

--

Since my internet foo failed me, and the only workable example of a H2C client I can find was in the actual go test suite I’m going to lay out what I
discovered about H2C support in golang here.

First, incase you may be wondering what H2C is, It is essentially HTTP/2 but without TLS. H2C is understandably not widely publicized as HTTP/2 with TLS is more secure and not open to some types of attacks. However H2C isn’t without it’s use cases, for instance GRPC uses H2C when you build a client with grpc.WithInsecure().

Second, is that the standard golang code supports HTTP2 but does not directly support H2C. H2C support only exists in the golang.org/x/net/http2/h2c package. You can make your HTTP server H2C capable by wrapping your handler or mux with h2c.NewHandler() like so.

The above code allows the server to support H2C upgrade and H2C prior knowledge along with standard HTTP/2 and HTTP/1.1 that golang natively supports.

If you don’t care about supporting HTTP/1.1 then you can run this code which only supports H2C prior knowledge.

Once you have a running server you can test your server by installing curl-openssl and use curl to test your H2C enabled server.

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

Connect via HTTP1.1 then upgrade to 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
<
* Connection #0 to host localhost left intact
Hello, /, http: true

Connect 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
<
* Connection #0 to host localhost left intact
Hello, /, http: true

Now, Remember when I said that the golang standard library does not support H2C? While that is technically correct there is a workaround to get the golang standard HTTP/2 client to connect to an H2C enabled server.

To do so you have to override DialTLS and set the super secret AllowHTTP flag.

Although this all looks a little wonky it actually works really well and
performs nicely in production environments.

A complete working example is available here http://github.com/thrawn01/h2c-golang-example

--

--

Derrick Wippler

Formerly @ Rackspace Cloud Block Storage, then @ Mailgun.com. With over 20+ years in the tech industry as a Developer, Entrepreneur, and Herder of Cats