Browse Source
* reverse proxy: Support more h2 stream scenarios (#3556) * reverse proxy: add integration test for better h2 stream (#3556) * reverse proxy: adjust comments as francislavoie suggests * link to issue #3556 in the commentsmaster
committed by
GitHub
3 changed files with 214 additions and 0 deletions
@ -0,0 +1,201 @@ |
|||
package integration |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"io" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"net/http/httputil" |
|||
"net/url" |
|||
"strings" |
|||
"testing" |
|||
"time" |
|||
|
|||
"github.com/caddyserver/caddy/v2/caddytest" |
|||
"golang.org/x/net/http2" |
|||
"golang.org/x/net/http2/h2c" |
|||
) |
|||
|
|||
// (see https://github.com/caddyserver/caddy/issues/3556 for use case)
|
|||
func TestH2ToH2CStream(t *testing.T) { |
|||
tester := caddytest.NewTester(t) |
|||
tester.InitServer(` |
|||
{ |
|||
"apps": { |
|||
"http": { |
|||
"http_port": 9080, |
|||
"https_port": 9443, |
|||
"servers": { |
|||
"srv0": { |
|||
"listen": [ |
|||
":9443" |
|||
], |
|||
"routes": [ |
|||
{ |
|||
"handle": [ |
|||
{ |
|||
"handler": "reverse_proxy", |
|||
"transport": { |
|||
"protocol": "http", |
|||
"compression": false, |
|||
"versions": [ |
|||
"h2c", |
|||
"2" |
|||
] |
|||
}, |
|||
"upstreams": [ |
|||
{ |
|||
"dial": "localhost:54321" |
|||
} |
|||
] |
|||
} |
|||
], |
|||
"match": [ |
|||
{ |
|||
"path": [ |
|||
"/tov2ray" |
|||
] |
|||
} |
|||
] |
|||
} |
|||
], |
|||
"tls_connection_policies": [ |
|||
{ |
|||
"certificate_selection": { |
|||
"any_tag": ["cert0"] |
|||
}, |
|||
"default_sni": "a.caddy.localhost" |
|||
} |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
"tls": { |
|||
"certificates": { |
|||
"load_files": [ |
|||
{ |
|||
"certificate": "/a.caddy.localhost.crt", |
|||
"key": "/a.caddy.localhost.key", |
|||
"tags": [ |
|||
"cert0" |
|||
] |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
"pki": { |
|||
"certificate_authorities" : { |
|||
"local" : { |
|||
"install_trust": false |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
`, "json") |
|||
|
|||
expectedBody := "some data to be echoed" |
|||
// start the server
|
|||
server := testH2ToH2CStreamServeH2C(t) |
|||
go server.ListenAndServe() |
|||
defer func() { |
|||
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) |
|||
defer cancel() |
|||
server.Shutdown(ctx) |
|||
}() |
|||
|
|||
r, w := io.Pipe() |
|||
req := &http.Request{ |
|||
Method: "PUT", |
|||
Body: ioutil.NopCloser(r), |
|||
URL: &url.URL{ |
|||
Scheme: "https", |
|||
Host: "127.0.0.1:9443", |
|||
Path: "/tov2ray", |
|||
}, |
|||
Proto: "HTTP/2", |
|||
ProtoMajor: 2, |
|||
ProtoMinor: 0, |
|||
Header: make(http.Header), |
|||
} |
|||
// Disable any compression method from server.
|
|||
req.Header.Set("Accept-Encoding", "identity") |
|||
|
|||
resp := tester.AssertResponseCode(req, 200) |
|||
if 200 != resp.StatusCode { |
|||
return |
|||
} |
|||
go func() { |
|||
fmt.Fprint(w, expectedBody) |
|||
w.Close() |
|||
}() |
|||
|
|||
defer resp.Body.Close() |
|||
bytes, err := ioutil.ReadAll(resp.Body) |
|||
if err != nil { |
|||
t.Fatalf("unable to read the response body %s", err) |
|||
} |
|||
|
|||
body := string(bytes) |
|||
|
|||
if !strings.Contains(body, expectedBody) { |
|||
t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body) |
|||
} |
|||
return |
|||
} |
|||
|
|||
func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server { |
|||
h2s := &http2.Server{} |
|||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
|||
rstring, err := httputil.DumpRequest(r, false) |
|||
if err == nil { |
|||
t.Logf("h2c server received req: %s", rstring) |
|||
} |
|||
// We only accept HTTP/2!
|
|||
if r.ProtoMajor != 2 { |
|||
t.Error("Not a HTTP/2 request, rejected!") |
|||
w.WriteHeader(http.StatusInternalServerError) |
|||
return |
|||
} |
|||
|
|||
if r.Host != "127.0.0.1:9443" { |
|||
t.Errorf("r.Host doesn't match, %v!", r.Host) |
|||
w.WriteHeader(http.StatusNotFound) |
|||
return |
|||
} |
|||
|
|||
if !strings.HasPrefix(r.URL.Path, "/tov2ray") { |
|||
w.WriteHeader(http.StatusNotFound) |
|||
return |
|||
} |
|||
|
|||
w.Header().Set("Cache-Control", "no-store") |
|||
w.WriteHeader(200) |
|||
if f, ok := w.(http.Flusher); ok { |
|||
f.Flush() |
|||
} |
|||
|
|||
buf := make([]byte, 4*1024) |
|||
|
|||
for { |
|||
n, err := r.Body.Read(buf) |
|||
if n > 0 { |
|||
w.Write(buf[:n]) |
|||
} |
|||
|
|||
if err != nil { |
|||
if err == io.EOF { |
|||
r.Body.Close() |
|||
} |
|||
break |
|||
} |
|||
} |
|||
}) |
|||
|
|||
server := &http.Server{ |
|||
Addr: "127.0.0.1:54321", |
|||
Handler: h2c.NewHandler(handler, h2s), |
|||
} |
|||
return server |
|||
} |
Loading…
Reference in new issue