Golang H2C Server 不写全文。 Chrome 说:"Server reset stream"。仅发生在 http2 连接上
Golang H2C Server doesn't write full body. Chrome says: "Server reset stream". Only occurs over http2 connection
Go 的最新版本 (1.153)
下面是再现性的代码。请尝试访问 https://easy-dp.ngrok.io 以查看问题。
这是我所做的:
- 创建一个访问 Gzipped/Br 编码内容的反向代理
- 请求公开可用 URL,我刚抓取 Google 分析
- 尝试使用 proxy.modifyresponse 函数通过 http2 连接对响应进行编码和解码
- 观看内容被丢弃。
但是,这只会在以下情况下发生:
- 在 SSL 下,与 https://easy-dp.ngrok.io
一样
- 当运行一个
proxy.ModifyResponse
函数时
- 解压缩并重新压缩正文(例如,只需读取正文并将其重写为新字节即可)
package main
import (
"bytes"
"compress/gzip"
"fmt"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"io/ioutil"
"net/http"
"net/http/httputil"
"strconv"
"time"
)
func ForwardAnalytics(req *http.Request) {
req.URL.Scheme = "https"
req.URL.Host = "www.google-analytics.com"
req.Host = "www.google-analytics.com"
req.URL.Path = "/analytics.js"
req.Header.Set("Accept-Encoding", "gzip")
}
func ModifyAnalytics(r *http.Response) error {
bytesFromBody, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if err != nil {
return nil
}
if r.Header.Get("Content-Encoding") == "gzip" {
gzipReader, err := gzip.NewReader(bytes.NewBuffer(bytesFromBody))
if err != nil {
return nil
}
defer gzipReader.Close()
readableBytes, err := ioutil.ReadAll(gzipReader)
var b bytes.Buffer
gzipWriter, err := gzip.NewWriterLevel(&b, gzip.DefaultCompression)
if err != nil {
return nil
}
defer gzipWriter.Close()
writtenLen, err := gzipWriter.Write(readableBytes)
fmt.Println("Wrote ", writtenLen)
if err != nil {
return nil
}
r.ContentLength = int64(len(readableBytes))
r.Header.Set("Content-Length", strconv.FormatInt(int64(len(readableBytes)), 10))
r.Body = ioutil.NopCloser(&b)
return nil
} else {
return nil
}
}
func handleProxy(w http.ResponseWriter, req *http.Request) {
proxy := httputil.ReverseProxy{
Director: ForwardAnalytics
}
proxy.ModifyResponse = ModifyAnalytics
proxy.ServeHTTP(w, req)
}
func main() {
h2s := &http2.Server{
IdleTimeout: 20 * time.Second,
}
mux := http.NewServeMux()
mux.HandleFunc( "/", handleProxy)
s := &http.Server{
ReadHeaderTimeout: 20 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
Addr: "localhost:8456",
Handler: h2c.NewHandler(mux, h2s),
}
s.ListenAndServe()
}
您希望看到什么?
我希望看到在 H2C 连接上打开字节、修改字节以及更新响应主体的功能
你看到了什么?
有两件事值得注意:
- Chrome 给出了一个很好的小错误,扩展了正在发生的事情
{"params":{"description":"Server reset stream.","net_error":"ERR_HTTP2_PROTOCOL_ERROR","stream_id":5},"phase":0,"source":{"id":1493828,"start_time":"732370299","type":1},"time":"732375561","type":224},
- 在正常的http连接下,没有问题,但在https连接下,脚本可能会或可能不会打印到一定长度。有时根本不打印,有时打印大约 30%。
这是一个跨浏览器问题。
The Content-Length header indicates the size of the entity body in the message, in bytes. The size includes any content encodings (the Content-Length of a gzip-compressed text file will be the compressed size, not the original size).
我以为我已经尝试过了,但由于我关闭 gzip 编写器的方式,我将 运行 保留在 ERR_CONTENT_LENGTH_MISMATCH
中。
最终处理程序如下所示:
if r.Header.Get("Content-Encoding") == "gzip" {
gzipReader, err := gzip.NewReader(bytes.NewBuffer(bytesFromBody))
if err != nil {
return nil
}
defer gzipReader.Close()
readableBytes, err := ioutil.ReadAll(gzipReader)
var b bytes.Buffer
gzipWriter, err := gzip.NewWriterLevel(&b, gzip.DefaultCompression)
if err != nil {
return nil
}
writtenLen, err := gzipWriter.Write(readableBytes)
gzipWriter.Close() // This was the culprit. It needed to be closed here
fmt.Println("Wrote ", writtenLen)
if err != nil {
return nil
}
r.ContentLength = int64(b.Len())
r.Header.Set("Content-Length", strconv.FormatInt(int64(b.Len()), 10))
r.Body = ioutil.NopCloser(&b)
return nil
}
Go 的最新版本 (1.153)
下面是再现性的代码。请尝试访问 https://easy-dp.ngrok.io 以查看问题。
这是我所做的:
- 创建一个访问 Gzipped/Br 编码内容的反向代理
- 请求公开可用 URL,我刚抓取 Google 分析
- 尝试使用 proxy.modifyresponse 函数通过 http2 连接对响应进行编码和解码
- 观看内容被丢弃。
但是,这只会在以下情况下发生:
- 在 SSL 下,与 https://easy-dp.ngrok.io 一样
- 当运行一个
proxy.ModifyResponse
函数时 - 解压缩并重新压缩正文(例如,只需读取正文并将其重写为新字节即可)
package main
import (
"bytes"
"compress/gzip"
"fmt"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"io/ioutil"
"net/http"
"net/http/httputil"
"strconv"
"time"
)
func ForwardAnalytics(req *http.Request) {
req.URL.Scheme = "https"
req.URL.Host = "www.google-analytics.com"
req.Host = "www.google-analytics.com"
req.URL.Path = "/analytics.js"
req.Header.Set("Accept-Encoding", "gzip")
}
func ModifyAnalytics(r *http.Response) error {
bytesFromBody, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if err != nil {
return nil
}
if r.Header.Get("Content-Encoding") == "gzip" {
gzipReader, err := gzip.NewReader(bytes.NewBuffer(bytesFromBody))
if err != nil {
return nil
}
defer gzipReader.Close()
readableBytes, err := ioutil.ReadAll(gzipReader)
var b bytes.Buffer
gzipWriter, err := gzip.NewWriterLevel(&b, gzip.DefaultCompression)
if err != nil {
return nil
}
defer gzipWriter.Close()
writtenLen, err := gzipWriter.Write(readableBytes)
fmt.Println("Wrote ", writtenLen)
if err != nil {
return nil
}
r.ContentLength = int64(len(readableBytes))
r.Header.Set("Content-Length", strconv.FormatInt(int64(len(readableBytes)), 10))
r.Body = ioutil.NopCloser(&b)
return nil
} else {
return nil
}
}
func handleProxy(w http.ResponseWriter, req *http.Request) {
proxy := httputil.ReverseProxy{
Director: ForwardAnalytics
}
proxy.ModifyResponse = ModifyAnalytics
proxy.ServeHTTP(w, req)
}
func main() {
h2s := &http2.Server{
IdleTimeout: 20 * time.Second,
}
mux := http.NewServeMux()
mux.HandleFunc( "/", handleProxy)
s := &http.Server{
ReadHeaderTimeout: 20 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
Addr: "localhost:8456",
Handler: h2c.NewHandler(mux, h2s),
}
s.ListenAndServe()
}
您希望看到什么?
我希望看到在 H2C 连接上打开字节、修改字节以及更新响应主体的功能
你看到了什么?
有两件事值得注意:
- Chrome 给出了一个很好的小错误,扩展了正在发生的事情
{"params":{"description":"Server reset stream.","net_error":"ERR_HTTP2_PROTOCOL_ERROR","stream_id":5},"phase":0,"source":{"id":1493828,"start_time":"732370299","type":1},"time":"732375561","type":224},
- 在正常的http连接下,没有问题,但在https连接下,脚本可能会或可能不会打印到一定长度。有时根本不打印,有时打印大约 30%。
这是一个跨浏览器问题。
The Content-Length header indicates the size of the entity body in the message, in bytes. The size includes any content encodings (the Content-Length of a gzip-compressed text file will be the compressed size, not the original size).
我以为我已经尝试过了,但由于我关闭 gzip 编写器的方式,我将 运行 保留在 ERR_CONTENT_LENGTH_MISMATCH
中。
最终处理程序如下所示:
if r.Header.Get("Content-Encoding") == "gzip" {
gzipReader, err := gzip.NewReader(bytes.NewBuffer(bytesFromBody))
if err != nil {
return nil
}
defer gzipReader.Close()
readableBytes, err := ioutil.ReadAll(gzipReader)
var b bytes.Buffer
gzipWriter, err := gzip.NewWriterLevel(&b, gzip.DefaultCompression)
if err != nil {
return nil
}
writtenLen, err := gzipWriter.Write(readableBytes)
gzipWriter.Close() // This was the culprit. It needed to be closed here
fmt.Println("Wrote ", writtenLen)
if err != nil {
return nil
}
r.ContentLength = int64(b.Len())
r.Header.Set("Content-Length", strconv.FormatInt(int64(b.Len()), 10))
r.Body = ioutil.NopCloser(&b)
return nil
}