@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 133 lines 4.66 KB
Raw
1 package integration
2
3 import (
4 "context"
5 "fmt"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "testing"
10 "time"
11
12 "github.com/caddyserver/caddy/v2/caddytest"
13 )
14
15 // A timed-out upload must get an error response without sending the remainder
16 // of its body. HTTP/1 drains unread bodies smaller than 256 KiB before replying;
17 // clearing the expired read deadline makes that drain wait for the client.
18 func TestReverseProxyRequestBodyTimeout(t *testing.T) {
19 for _, proto := range []int{1, 2} {
20 for _, tc := range []struct {
21 name string
22 timeouts string
23 trickle bool
24 handler string
25 }{
26 {"hard stalled", `"read_timeout": 300000000, "read_idle_timeout": 60000000000`, false, ""},
27 {"hard trickled", `"read_timeout": 300000000, "read_idle_timeout": 60000000000`, true, ""},
28 {"idle stalled", `"read_idle_timeout": 300000000`, false, ""},
29 {"minimum rate trickled", `"read_idle_timeout": 300000000, "read_min_rate": 65536`, true, ""},
30 {"route stalled", `"read_idle_timeout": 60000000000`, false, `{"handler": "timeouts", "read_timeout": 300000000},`},
31 } {
32 t.Run(fmt.Sprintf("HTTP%d/%s", proto, tc.name), func(t *testing.T) {
33 type uploadResult struct {
34 n int64
35 err error
36 }
37 upstreamRead := make(chan uploadResult, 1)
38 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
39 n, err := io.Copy(io.Discard, r.Body)
40 upstreamRead <- uploadResult{n, err}
41 w.WriteHeader(http.StatusNoContent)
42 }))
43 defer upstream.Close()
44 tester := caddytest.NewTester(t)
45 tester.InitServer(fmt.Sprintf(`{
46 "admin": {"listen": "localhost:2999", "config": {"persist": false}},
47 "apps": {
48 "tls": {"certificates": {"load_files": [{
49 "certificate": "/caddy.localhost.crt", "key": "/caddy.localhost.key"
50 }]}},
51 "http": {"grace_period": 1, "servers": {"srv0": {
52 "listen": ["127.0.0.1:9443"],
53 "automatic_https": {"disable": true},
54 "tls_connection_policies": [{}],
55 %s,
56 "routes": [{"handle": [%s{"handler": "reverse_proxy",
57 "upstreams": [{"dial": %q}]
58 }]}]
59 }}}
60 }
61 }`, tc.timeouts, tc.handler, upstream.Listener.Addr().String()), "json")
62 if t.Failed() {
63 t.FailNow()
64 }
65 transport := tester.Client.Transport.(*http.Transport)
66 if proto == 1 {
67 transport.ForceAttemptHTTP2 = false
68 }
69 defer transport.CloseIdleConnections()
70 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
71 defer cancel()
72 pr, pw := io.Pipe()
73 defer pr.Close()
74 defer pw.Close()
75 stopClose := context.AfterFunc(ctx, func() { pr.CloseWithError(ctx.Err()) })
76 defer stopClose()
77 writerDone := make(chan struct{})
78 go func() {
79 defer close(writerDone)
80 if _, err := pw.Write(make([]byte, 1024)); err != nil {
81 return
82 }
83 if !tc.trickle {
84 return // Leave the pipe open so the request body stalls.
85 }
86 ticker := time.NewTicker(25 * time.Millisecond)
87 defer ticker.Stop()
88 for {
89 select {
90 case <-ctx.Done():
91 return
92 case <-ticker.C:
93 if _, err := pw.Write([]byte("x")); err != nil {
94 return
95 }
96 }
97 }
98 }()
99 defer func() {
100 cancel()
101 pr.Close()
102 <-writerDone
103 }()
104 req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://a.caddy.localhost:9443", pr)
105 if err != nil {
106 t.Fatal(err)
107 }
108 req.ContentLength = 100 << 10 // Below net/http's post-handler drain limit.
109 resp, err := tester.Client.Do(req)
110 if err != nil {
111 t.Fatalf("did not receive a response to the timed-out upload: %v", err)
112 }
113 defer resp.Body.Close()
114 if resp.ProtoMajor != proto {
115 t.Fatalf("got HTTP/%d, want HTTP/%d", resp.ProtoMajor, proto)
116 }
117 // A read timeout can also cancel the request context, so the proxy
118 // can classify it as a canceled request rather than a gateway error.
119 if resp.StatusCode != 499 && resp.StatusCode != http.StatusBadGateway && resp.StatusCode != http.StatusGatewayTimeout {
120 t.Errorf("unexpected timeout response status: %d", resp.StatusCode)
121 }
122 select {
123 case result := <-upstreamRead:
124 if result.err == nil || result.n < 1024 || result.n >= req.ContentLength {
125 t.Errorf("upstream upload was not interrupted: read %d bytes, error %v", result.n, result.err)
126 }
127 case <-ctx.Done():
128 t.Fatal("upstream upload was not interrupted")
129 }
130 })
131 }
132 }
133 }