Golang中net/http获取Early Hints(103)的实现方法

Golang中net/http获取Early Hints(103)的实现方法 我尝试检查:https://early-hints.fastlylabs.com 并希望获取103状态码,或者至少检查它是否存在,但103状态码当然不是持久性的,一旦200状态码被触发就会被覆盖。 有没有办法捕获103状态码?

我尝试了以下代码,但它只返回了200(OK)状态码:

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	resp, err := http.Head("https://early-hints.fastlylabs.com/")
	if err != nil {
		log.Fatalln(err)
	}

	fmt.Println(resp.Status)
}

但它只返回:

200 OK

当我尝试:

package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httputil"
)

func main() {
	resp, err := http.Head("https://early-hints.fastlylabs.com/")
	if err != nil {
		log.Fatalln(err)
	}

	b, err := httputil.DumpResponse(resp, false)
	if err != nil {
		log.Fatalln(err)
	}

	fmt.Println(string(b))
}

我得到的响应头类似于:

HTTP/2.0 200 OK
[...]

但从未看到103(EarlyHint)状态码。 有没有办法获取103状态码(如果被触发)并在它变成200之前存储它,就像使用cURL时那样?

curl -X HEAD -I https://early-hints.fastlylabs.com/ 2>&1
HTTP/2 103
origin-trial: [#########]
link: </hinted.png>; rel=preload; as=image

HTTP/2 200
[...]

在这里我看到了103状态码,但我无法让Go(net/http)显示它。 也许这个功能缺失了,或者目前还不可能实现,尽管它们在这里有定义:http package - net/http - Go Packages

StatusEarlyHints = 103 // RFC 8297

顺便说一下,我使用的是Go v1.19.1,这是最新的版本。


更多关于Golang中net/http获取Early Hints(103)的实现方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

好的,我做到了!感谢你超级有用的回复!

现在我需要调整一下。

更多关于Golang中net/http获取Early Hints(103)的实现方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


有没有办法捕获103状态码?

我还没有尝试过,但阅读了 net/http: unexpected 1XX status codes are not handled well · Issue #17739 · golang/go · GitHubnet/http: support for the 103 status code · Issue #51914 · golang/go · GitHub 后,看起来你需要:

我预计你会在注册了 Got1xxResponse 的函数中收到关于103响应的回调。

非常感谢您的回复! 😊

skillian:

net/http: support for the 103 status code · Issue #51914 · golang/go · GitHub

是的,我见过这个问题,但对我来说,这似乎只关乎“发送”103状态码。我并不想发送,实际上只是想检查是否存在103状态码。

skillian:

我期望您会在注册了 Got1xxResponse 的函数中收到关于103响应的回调。

哇,这听起来比我想象的要复杂得多,但我会试一试。 等我尝试过后,我会回来汇报情况。

在Go中获取Early Hints(103状态码)需要使用http.Transport并处理http.Response的中间状态。标准库的http.Client默认不会暴露中间状态码,但可以通过自定义RoundTripper来捕获。以下是实现方法:

package main

import (
    "fmt"
    "log"
    "net/http"
)

type earlyHintCapturingTransport struct {
    transport http.RoundTripper
}

func (t *earlyHintCapturingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    if t.transport == nil {
        t.transport = http.DefaultTransport
    }
    
    resp, err := t.transport.RoundTrip(req)
    if err != nil {
        return nil, err
    }
    
    // 检查是否为103状态码
    if resp.StatusCode == http.StatusEarlyHints {
        fmt.Printf("捕获到Early Hint (103): %v\n", resp.Header)
        // 这里可以存储103响应
        // 继续获取最终响应
        finalResp, err := t.transport.RoundTrip(req)
        return finalResp, err
    }
    
    return resp, nil
}

func main() {
    client := &http.Client{
        Transport: &earlyHintCapturingTransport{},
    }
    
    req, err := http.NewRequest("HEAD", "https://early-hints.fastlylabs.com/", nil)
    if err != nil {
        log.Fatal(err)
    }
    
    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
    
    fmt.Printf("最终状态码: %d %s\n", resp.StatusCode, resp.Status)
}

对于更精细的控制,可以使用http2.Transport并实现自定义的http2.ClientConn处理:

package main

import (
    "context"
    "crypto/tls"
    "fmt"
    "log"
    "net/http"
    "golang.org/x/net/http2"
)

type earlyHintHandler struct {
    hints []*http.Response
}

func (h *earlyHintHandler) HandleResponse(resp *http.Response) error {
    if resp.StatusCode == http.StatusEarlyHints {
        h.hints = append(h.hints, resp)
        fmt.Printf("捕获到103 Early Hint\n")
        fmt.Printf("头部: %v\n", resp.Header)
        return nil // 继续处理
    }
    return nil
}

func main() {
    tr := &http2.Transport{
        TLSClientConfig: &tls.Config{
            InsecureSkipVerify: false,
        },
    }
    
    client := &http.Client{
        Transport: tr,
    }
    
    // 创建请求
    req, err := http.NewRequestWithContext(context.Background(), 
        "HEAD", "https://early-hints.fastlylabs.com/", nil)
    if err != nil {
        log.Fatal(err)
    }
    
    // 发送请求
    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
    
    fmt.Printf("最终响应: %d %s\n", resp.StatusCode, resp.Status)
}

如果需要直接访问HTTP/2帧,可以使用更低层级的实现:

package main

import (
    "crypto/tls"
    "fmt"
    "log"
    "net"
    "golang.org/x/net/http2"
)

func main() {
    addr := "early-hints.fastlylabs.com:443"
    
    conn, err := tls.Dial("tcp", addr, &tls.Config{
        NextProtos: []string{"h2"},
        ServerName: "early-hints.fastlylabs.com",
    })
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()
    
    http2Conn, err := http2.NewClientConn(conn, &http2.ClientConnOpts{})
    if err != nil {
        log.Fatal(err)
    }
    
    req, err := http.NewRequest("HEAD", "https://early-hints.fastlylabs.com/", nil)
    if err != nil {
        log.Fatal(err)
    }
    
    // 发送请求并处理响应
    resp, err := http2Conn.RoundTrip(req)
    if err != nil {
        log.Fatal(err)
    }
    
    // 注意:这里仍然可能只看到最终状态码
    // 需要更底层的HTTP/2实现来捕获中间帧
    fmt.Printf("响应状态: %d\n", resp.StatusCode)
}

对于完全控制HTTP/2帧的处理,需要实现自定义的http2.ClientConn和帧处理器:

package main

import (
    "bufio"
    "crypto/tls"
    "fmt"
    "log"
    "net"
    "golang.org/x/net/http2"
    "golang.org/x/net/http2/hpack"
)

type frameHandler struct{}

func (h *frameHandler) HandleFrame(f http2.Frame) error {
    switch f := f.(type) {
    case *http2.HeadersFrame:
        // 可以在这里解析头部帧,检查状态码
        fmt.Printf("收到HeadersFrame: %v\n", f)
    case *http2.PushPromiseFrame:
        fmt.Printf("收到PushPromiseFrame: %v\n", f)
    }
    return nil
}

func main() {
    conn, err := tls.Dial("tcp", "early-hints.fastlylabs.com:443", &tls.Config{
        NextProtos: []string{"h2"},
    })
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()
    
    fr := http2.NewFramer(bufio.NewWriter(conn), bufio.NewReader(conn))
    
    // 发送连接序言
    fr.WriteSettings()
    fr.WriteWindowUpdate(0, 65535)
    fr.WritePriority(0, http2.PriorityParam{})
    
    // 创建HEAD请求的HEADERS帧
    var buf []byte
    hpackEnc := hpack.NewEncoder(&buf)
    hpackEnc.WriteField(hpack.HeaderField{Name: ":method", Value: "HEAD"})
    hpackEnc.WriteField(hpack.HeaderField{Name: ":scheme", Value: "https"})
    hpackEnc.WriteField(hpack.HeaderField{Name: ":authority", Value: "early-hints.fastlylabs.com"})
    hpackEnc.WriteField(hpack.HeaderField{Name: ":path", Value: "/"})
    
    // 这个示例展示了底层操作,实际捕获103需要解析HEADERS帧中的:status伪头部
}

这些示例展示了在Go中捕获Early Hints的不同方法。最实用的方法是第一个示例,通过自定义RoundTripper来拦截中间响应。

回到顶部