/caddytest/integration/stream_test.go
Go | 436 lines | 384 code | 34 blank | 18 comment | 54 complexity | 70680f265208bfd1c014447c8d3d2cd2 MD5 | raw file
- package integration
- import (
- "compress/gzip"
- "context"
- "crypto/rand"
- "fmt"
- "io"
- "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: io.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 := io.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
- }
- // (see https://github.com/caddyserver/caddy/issues/3606 for use case)
- func TestH2ToH1ChunkedResponse(t *testing.T) {
- tester := caddytest.NewTester(t)
- tester.InitServer(`
- {
- "logging": {
- "logs": {
- "default": {
- "level": "DEBUG"
- }
- }
- },
- "apps": {
- "http": {
- "http_port": 9080,
- "https_port": 9443,
- "servers": {
- "srv0": {
- "listen": [
- ":9443"
- ],
- "routes": [
- {
- "handle": [
- {
- "handler": "subroute",
- "routes": [
- {
- "handle": [
- {
- "encodings": {
- "gzip": {}
- },
- "handler": "encode"
- }
- ]
- },
- {
- "handle": [
- {
- "handler": "reverse_proxy",
- "upstreams": [
- {
- "dial": "localhost:54321"
- }
- ]
- }
- ],
- "match": [
- {
- "path": [
- "/tov2ray"
- ]
- }
- ]
- }
- ]
- }
- ],
- "terminal": true
- }
- ],
- "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")
- // need a large body here to trigger caddy's compression, larger than gzip.miniLength
- expectedBody, err := GenerateRandomString(1024)
- if err != nil {
- t.Fatalf("generate expected body failed, err: %s", err)
- }
- // start the server
- server := testH2ToH1ChunkedResponseServeH1(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: io.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),
- }
- // underlying transport will automaticlly add gzip
- // req.Header.Set("Accept-Encoding", "gzip")
- go func() {
- fmt.Fprint(w, expectedBody)
- w.Close()
- }()
- resp := tester.AssertResponseCode(req, 200)
- if 200 != resp.StatusCode {
- return
- }
- defer resp.Body.Close()
- bytes, err := io.ReadAll(resp.Body)
- if err != nil {
- t.Fatalf("unable to read the response body %s", err)
- }
- body := string(bytes)
- if body != expectedBody {
- t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
- }
- return
- }
- func testH2ToH1ChunkedResponseServeH1(t *testing.T) *http.Server {
- handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- 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
- }
- defer r.Body.Close()
- bytes, err := io.ReadAll(r.Body)
- if err != nil {
- t.Fatalf("unable to read the response body %s", err)
- }
- n := len(bytes)
- var writer io.Writer
- if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
- gw, err := gzip.NewWriterLevel(w, 5)
- if err != nil {
- t.Error("can't return gzip data")
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- defer gw.Close()
- writer = gw
- w.Header().Set("Content-Encoding", "gzip")
- w.Header().Del("Content-Length")
- w.WriteHeader(200)
- } else {
- writer = w
- }
- if n > 0 {
- writer.Write(bytes[:])
- }
- })
- server := &http.Server{
- Addr: "127.0.0.1:54321",
- Handler: handler,
- }
- return server
- }
- // GenerateRandomBytes returns securely generated random bytes.
- // It will return an error if the system's secure random
- // number generator fails to function correctly, in which
- // case the caller should not continue.
- func GenerateRandomBytes(n int) ([]byte, error) {
- b := make([]byte, n)
- _, err := rand.Read(b)
- // Note that err == nil only if we read len(b) bytes.
- if err != nil {
- return nil, err
- }
- return b, nil
- }
- // GenerateRandomString returns a securely generated random string.
- // It will return an error if the system's secure random
- // number generator fails to function correctly, in which
- // case the caller should not continue.
- func GenerateRandomString(n int) (string, error) {
- const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
- bytes, err := GenerateRandomBytes(n)
- if err != nil {
- return "", err
- }
- for i, b := range bytes {
- bytes[i] = letters[b%byte(len(letters))]
- }
- return string(bytes), nil
- }