如何从 net/http 请求中读取 HTTP/2 推送帧?

huangapple go评论93阅读模式
英文:

How can I read HTTP/2 push frames from a net/http request

问题

我正在尝试编写一个Go客户端来测试我们的http/2基础设施。我想要向https://mydomain.tld/somePage发出一个http请求,并期望收到一个带有多个推送资源的html响应。我希望确保这些推送是成功的,如果不成功则失败。

目前我不清楚标准库的任何部分是否提供了我所需的功能。

我可以查看响应并检查协议版本以检测http2。

我可以在像https://http2-push.appspot.com/这样发送推送的网站的响应中看到Link头,但我对Link头和实际的Push Promise帧之间的关系还不太清楚。你可以通过http 1.1获取链接头,所以我不确定仅凭这一点是否能确保推送会发生。

http2包有一个更低级的Framer接口,我可以利用它来验证原始帧,但是,老实说,我不知道如何设置并向其发出初始请求。

是否有任何示例展示了如何使用Go客户端验证http2推送资源的正确配置?

英文:

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?

答案1

得分: 4

使用golang.org/x/net/http2中的Framer并不难,只要我们能够获取http.Client自然读取的字节的副本。我们可以通过实现自己的net.Conn来实现这一点。

我在下面的程序中取得了一些进展,但是我没有看到预期的PUSH_PROMISE帧。经过一番搜索,我发现Go客户端明确禁用了Push。在这种情况下,服务器不允许发送这些帧。我没有看到明显的方法来更改该设置(除非修改stdlib)。

不过,我还是分享一下我的代码。也许我错过了一些简单的东西,毕竟让它工作起来。

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
}
英文:

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
}

答案2

得分: 1

一个补丁已经提交进行审查。

"http2: 支持在客户端消费PUSH_PROMISE流"

(该GitHub问题的里程碑为"未计划",希望这不会在审查队列中给它带来显著的优先级降低。)

英文:

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.)

huangapple
  • 本文由 发表于 2017年5月9日 00:41:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/43852955.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定