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

Posted

技术标签:

【中文标题】如何从 net/http 请求中读取 HTTP/2 推送帧【英文标题】:How can I read HTTP/2 push frames from a net/http request 【发布时间】:2017-10-06 18:27:58 【问题描述】:

我正在尝试编写一个 Go 客户端来测试我们的 http/2 基础架构。我想向https://mydomain.tld/somePage 发出一个http 请求,并希望收到一个html 响应,以及几个推送的资源。我想确保这些推送成功,否则失败。

我不清楚标准库的任何部分是否公开了我需要的这个功能。

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

我可以在来自https://http2-push.appspot.com/ 等发送推送的网站的响应中看到Link 标头,但我不太清楚链接标头和实际推送承诺帧之间的关系。您可以通过 http 1.1 获取链接标头,因此我不确定仅此一项能否确保推送会发生。

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

是否有 go 客户端如何验证 http2 推送资源的正确配置的示例?

【问题讨论】:

我知道他们最近有一篇关于服务器推送的博文 (blog.golang.org/h2push),但我还没有看到任何关于客户端的信息。据我了解,H2 推送通过将响应(通过制造请求并将该请求和响应包括在一起)推送到将响应直接存储在其缓存中的客户端来工作。结果是当客户端发出请求而不是通过网络时,它引用其本地缓存。这预计将内置在支持 h2 的浏览器中,无论该语言的 http 客户端是否本机支持它都需要一些挖掘。 对。我实际上不需要客户端支持来接收推送,我只需要真正验证“如果我是浏览器,服务器配置为正确推送这些东西” 查看test suite 似乎需要私有方法(例如decodeHeader)才能实际获取该数据,而there isn't much discussion about a proper client API。 这有点令人沮丧@nothingmuch。也许答案是“未来某个时候”? 也许你的案例是一个很好的激励例子,可以鼓励在这方面取得进展?我鼓励您参与讨论,因为您的测试用例似乎是在开始使用此类功能时尝试验证的非常基本的事情,我认为值得提出。另一个想法是使用node-http2 之类的东西,我想从 Go 测试中调用它很容易,尽管我不确定是否值得拥有另一个开发堆栈,即使是机会主义。跨度> 【参考方案1】:

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

我在下面的程序中取得了一些进展,但是我没有看到预期的 PUSH_PROMISE 帧。经过一番挖掘,我发现了the Go client explicitly disables Push。在这种情况下,不允许服务器发送这些帧。我看不到更改该设置的明显方法(没有破解标准库)。

以为我仍然分享我的代码。也许我错过了一些简单的东西让它发挥作用。

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.TransportDialTLS: dialT(buf)
    client := &http.ClientTransport: 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 &tConnconn, 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

【讨论】:

一个好的开始。我讨厌不得不分叉 std lib 来做到这一点。也许我会稍微推动一下这个问题。【参考方案2】:

patch 已提交审核。

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

(github 问题具有里程碑“未计划”,希望不会显着降低其在审查队列中的优先级。)

【讨论】:

Go1.10 被冻结,这可能适用于 Go1.11 Go1.17 已发布。还在等待。据我所知,多年来,许多人都在寻找这个。提交几乎完成,但当时在 Golang 方面没有足够的审阅者。我认为 go 保证的问题之一是,一旦它们支持标准库中的 API,它们就会保证无限期地支持它——而客户端如何通过客户端 api 访问推送承诺充满了主观意见。一个允许更改的实验包会更有可能得到支持。

以上是关于如何从 net/http 请求中读取 HTTP/2 推送帧的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Golang 的 net/http 包读取流响应正文?

如何修复错误:net::ERR_HTTP2_PROTOCOL_ERROR

Chrome:从 net-internals 获取 HTTP/2 日志

Go Web 编程之 请求

使用多个项目创建付款时如何从请求中读取项目?

基于标准库 net/http 如何记录 HTTP 请求?