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
好的,我做到了!感谢你超级有用的回复!
现在我需要调整一下。
更多关于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 · GitHub 和 net/http: support for the 103 status code · Issue #51914 · golang/go · GitHub 后,看起来你需要:
- 使用
net/http/httptrace包创建一个*httptrace.ClientTrace - 将其
Got1xxResponse回调设置为一个函数,用于处理该103消息。 - 使用
httptrace.WithClientTrace创建一个包装了你的ClientTrace的新context.Context - 将创建的
context.Context传递给http.NewRequestWithContext,然后执行请求。
我预计你会在注册了 Got1xxResponse 的函数中收到关于103响应的回调。
非常感谢您的回复! 😊
skillian:
net/http: support for the 103 status code · Issue #51914 · golang/go · GitHub
是的,我见过这个问题,但对我来说,这似乎只关乎“发送”103状态码。我并不想发送,实际上只是想检查是否存在103状态码。
skillian:
- 使用
net/http/httptrace包创建一个*httptrace.ClientTrace- 将其
Got1xxResponse回调设置为一个您想要处理该103消息的函数。- 使用
httptrace.WithClientTrace创建一个包装了您的ClientTrace的新context.Context- 将创建的
context.Context传递给http.NewRequestWithContext,然后执行请求。我期望您会在注册了
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来拦截中间响应。

