Reputation: 23078
I am trying to write a Go client to test our http/2 infrastructure. I would like to make an http request to https://mydomain.tld/somePage
and expect to receive an html response, along with several pushed resources. I would like to ensure those pushes are successful, and fail if they are not.
It is not clear to me if any part of the standard library exposes this functionality I need.
I can look at the response and check the protocol version to detect http2.
I can see Link
headers in responses from sites like https://http2-push.appspot.com/ that send pushes, but I'm not quite clear on the relationship between Link headers and actual Push Promise frames. You can get link headers over http 1.1, so I'm not sure that alone ensures a push will happen.
The http2 package has a lower level Framer
interface that I may be able to leverage to verify the raw frames, but honestly, I have no idea how to set one up and issue the initial request to it.
Is there any example of how a go client can verify the proper configuration of http2 pushed resources?
Upvotes: 10
Views: 3493
Reputation: 959
A patch was submitted for review.
"http2: support consuming PUSH_PROMISE streams in the client"
(The github issue has milestone "Unplanned", which hopefully won't give it significantly less priority in the review queue.)
Upvotes: 1
Reputation: 31701
Using the Framer in golang.org/x/net/http2 isn't hard, if we can get a copy of the bytes that are read naturally by the http.Client. We can do that by implementing our own net.Conn.
I made some progress with the program below, however I did not see the expected PUSH_PROMISE frames. After some digging around I found that the Go client explicitly disables Push. Servers are not allowed to send those frames in this case. I don't see an obvious way to change that setting (short of hacking the stdlib).
Thought I still share my code. Perhaps I missed something simple to make it work after all.
package main
import (
"bytes"
"crypto/tls"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"golang.org/x/net/http2"
)
func main() {
buf := &bytes.Buffer{}
transport := &http2.Transport{DialTLS: dialT(buf)}
client := &http.Client{Transport: transport}
res, err := client.Get("https://http2-push.appspot.com/")
if err != nil {
log.Fatal(err)
}
res.Body.Close()
res.Write(os.Stdout)
framer := http2.NewFramer(ioutil.Discard, buf)
for {
f, err := framer.ReadFrame()
if err == io.EOF || err == io.ErrUnexpectedEOF {
break
}
switch err.(type) {
case nil:
log.Println(f)
case http2.ConnectionError:
// Ignore. There will be many errors of type "PROTOCOL_ERROR, DATA
// frame with stream ID 0". Presumably we are abusing the framer.
default:
log.Println(err, framer.ErrorDetail())
}
}
}
// dialT returns a connection that writes everything that is read to w.
func dialT(w io.Writer) func(network, addr string, cfg *tls.Config) (net.Conn, error) {
return func(network, addr string, cfg *tls.Config) (net.Conn, error) {
conn, err := tls.Dial(network, addr, cfg)
return &tConn{conn, w}, err
}
}
type tConn struct {
net.Conn
T io.Writer // receives everything that is read from Conn
}
func (w *tConn) Read(b []byte) (n int, err error) {
n, err = w.Conn.Read(b)
w.T.Write(b)
return
}
Upvotes: 4