Most Recent Version of Go (1.153)
Below is the code for reproducibility. Please try to access https://easy-dp.ngrok.io to see the issue.
Here's what I did:
- Create a Reverse Proxy accessing Gzipped/ Br encoded Content
- Request a publicly available URL, I just grabbed Google Analytics
- Attempt to encode and decode the response via an http2 connection with a proxy.modifyresponse function
- Watch as content is dropped.
However, this only occurs under the following conditions:
- Under SSL, like with https://easy-dp.ngrok.io
- When running a
proxy.ModifyResponse
function - Decompressing and re-compressing the body (for example, just reading and rewriting the body to new bytes works)
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()
}
What did you expect to see?
I expect to see the ability to open the bytes, modify them, and update the response body on an H2C connection
What did you see instead?
Two things of note happen:
- Chrome gives a nice little error that expands upon what's going on
{"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},
- Under the normal http connection, there's no problem, but under the https connection the script may or may not print out to a certain length. Sometimes it doesn't print at all, sometimes it prints about 30%.
This is a cross browser issue.