@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 1,640 lines 50.7 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package caddyhttp
16
17 import (
18 "bufio"
19 "bytes"
20 "errors"
21 "fmt"
22 "io"
23 "math"
24 "net/http"
25 "net/http/httptest"
26 "os"
27 "strings"
28 "sync"
29 "testing"
30 "testing/iotest"
31 "time"
32
33 "github.com/stretchr/testify/assert"
34 "github.com/stretchr/testify/require"
35 "go.uber.org/zap"
36 )
37
38 type readDeadlineRecorder struct {
39 *httptest.ResponseRecorder
40 mu sync.Mutex
41 deadlines []time.Time
42 failAt int
43 }
44
45 func (w *readDeadlineRecorder) SetReadDeadline(deadline time.Time) error {
46 w.mu.Lock()
47 defer w.mu.Unlock()
48 w.deadlines = append(w.deadlines, deadline)
49 if len(w.deadlines) == w.failAt {
50 return errors.New("deadline failure")
51 }
52 return nil
53 }
54
55 func (w *readDeadlineRecorder) snapshot() []time.Time {
56 w.mu.Lock()
57 defer w.mu.Unlock()
58 return append([]time.Time(nil), w.deadlines...)
59 }
60
61 type blockingReadCloser struct {
62 started chan struct{}
63 release chan struct{}
64 once sync.Once
65 }
66
67 func (r *blockingReadCloser) Read([]byte) (int, error) {
68 r.once.Do(func() { close(r.started) })
69 <-r.release
70 return 0, io.EOF
71 }
72
73 func (*blockingReadCloser) Close() error { return nil }
74
75 // pacedReader emits chunkCount chunks of chunkSize bytes, sleeping delay
76 // before each one, simulating a client that trickles a request body.
77 type pacedReader struct {
78 delay time.Duration
79 chunkSize int
80 chunkCount int
81 }
82
83 func (p *pacedReader) Read(b []byte) (int, error) {
84 if p.chunkCount <= 0 {
85 return 0, io.EOF
86 }
87 time.Sleep(p.delay)
88 p.chunkCount--
89 n := copy(b, make([]byte, p.chunkSize))
90 return n, nil
91 }
92
93 func TestIdleTimeoutReader(t *testing.T) {
94 const timeout = 150 * time.Millisecond
95
96 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
97 wrapped := &IdleTimeoutReader{
98 ReadCloser: r.Body,
99 Ctrl: http.NewResponseController(w),
100 Deadline: IdleDeadline{Timeout: timeout},
101 Logger: zap.NewNop(),
102 }
103 _, err := io.Copy(io.Discard, wrapped)
104 if err != nil {
105 http.Error(w, err.Error(), http.StatusRequestTimeout)
106 return
107 }
108 w.WriteHeader(http.StatusOK)
109 }))
110 defer srv.Close()
111
112 t.Run("slow but steadily progressing upload is not killed", func(t *testing.T) {
113 // each gap is well under timeout, but the cumulative transfer
114 // time is well over it; a hard deadline would kill this
115 body := &pacedReader{delay: timeout / 4, chunkSize: 8, chunkCount: 8}
116 resp, err := http.Post(srv.URL, "application/octet-stream", body)
117 require.NoError(t, err)
118 defer resp.Body.Close()
119 assert.Equal(t, http.StatusOK, resp.StatusCode)
120 })
121
122 t.Run("stalled upload is aborted", func(t *testing.T) {
123 // a single gap that exceeds timeout must trip the deadline
124 body := &pacedReader{delay: timeout * 4, chunkSize: 8, chunkCount: 2}
125 resp, err := http.Post(srv.URL, "application/octet-stream", body)
126 if err != nil {
127 // the connection may also be reset outright, which is fine
128 return
129 }
130 defer resp.Body.Close()
131 assert.NotEqual(t, http.StatusOK, resp.StatusCode)
132 })
133 }
134
135 func TestIdleTimeoutReaderClearsDeadlineAtEOF(t *testing.T) {
136 w := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
137 r := &IdleTimeoutReader{
138 ReadCloser: io.NopCloser(strings.NewReader("body")),
139 Ctrl: http.NewResponseController(w),
140 Deadline: IdleDeadline{Timeout: time.Second},
141 Logger: zap.NewNop(),
142 }
143
144 _, err := io.Copy(io.Discard, r)
145 require.NoError(t, err)
146 deadlines := w.snapshot()
147 require.NotEmpty(t, deadlines)
148 assert.True(t, deadlines[len(deadlines)-1].IsZero())
149 }
150
151 func TestIdleTimeoutReaderTerminalError(t *testing.T) {
152 for _, tc := range []struct {
153 name string
154 err error
155 keepDeadline bool
156 }{
157 {"timeout", os.ErrDeadlineExceeded, true},
158 {"wrapped timeout", fmt.Errorf("read body: %w", os.ErrDeadlineExceeded), true},
159 {"EOF", io.EOF, false},
160 {"other error", errors.New("read failed"), false},
161 } {
162 t.Run(tc.name, func(t *testing.T) {
163 deadline := time.Now().Add(-time.Second)
164 w := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
165 r := &IdleTimeoutReader{
166 ReadCloser: io.NopCloser(iotest.ErrReader(tc.err)),
167 Ctrl: http.NewResponseController(w),
168 Deadline: IdleDeadline{Timeout: time.Hour, HardDeadline: deadline},
169 Logger: zap.NewNop(),
170 DrainDeadline: true,
171 }
172 _, err := r.Read(make([]byte, 1))
173 require.ErrorIs(t, err, tc.err)
174 want := time.Time{}
175 if tc.keepDeadline {
176 want = deadline
177 }
178 deadlines := w.snapshot()
179 require.NotEmpty(t, deadlines)
180 assert.Equal(t, want, deadlines[len(deadlines)-1], "after Read")
181 r.HandlerDone()
182 deadlines = w.snapshot()
183 assert.Equal(t, want, deadlines[len(deadlines)-1], "after HandlerDone")
184
185 _, err = r.Read(make([]byte, 1))
186 require.ErrorIs(t, err, tc.err)
187 r.HandlerDone()
188 assert.Equal(t, deadlines, w.snapshot(), "late reads and repeated completion must not change deadlines")
189 })
190 }
191 }
192
193 // Only an expired deadline successfully installed for a draining request may
194 // survive a timeout; a failed replacement must not change that decision.
195 func TestIdleTimeoutReaderTimeoutDeadlineCleanup(t *testing.T) {
196 for _, tc := range []struct {
197 name string
198 drain bool
199 initial time.Duration
200 replacement time.Duration
201 failAt int
202 wrapped bool
203 wantClear bool
204 wantCalls int
205 }{
206 {name: "early timeout", drain: true, initial: time.Hour, wantClear: true, wantCalls: 2},
207 {name: "wrapped early timeout", drain: true, initial: time.Hour, wrapped: true, wantClear: true, wantCalls: 2},
208 {name: "expired without drain", initial: -time.Hour, wantClear: true, wantCalls: 2},
209 {name: "initial installation fails", drain: true, initial: -time.Hour, failAt: 1, wantCalls: 1},
210 {name: "failed expired replacement", drain: true, initial: time.Hour, replacement: -time.Hour, failAt: 2, wantClear: true, wantCalls: 3},
211 {name: "failed future replacement", drain: true, initial: -time.Hour, replacement: time.Hour, failAt: 2, wantCalls: 2},
212 {name: "failed clear retries at completion", drain: true, initial: time.Hour, failAt: 2, wantClear: true, wantCalls: 3},
213 } {
214 t.Run(tc.name, func(t *testing.T) {
215 w := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder(), failAt: tc.failAt}
216 terminalErr := error(os.ErrDeadlineExceeded)
217 if tc.wrapped {
218 terminalErr = fmt.Errorf("read body: %w", terminalErr)
219 }
220 r := &IdleTimeoutReader{
221 ReadCloser: io.NopCloser(iotest.ErrReader(terminalErr)),
222 Ctrl: http.NewResponseController(w),
223 Deadline: IdleDeadline{Timeout: tc.initial},
224 Logger: zap.NewNop(), DrainDeadline: tc.drain,
225 }
226 // Install once, then exercise a failed replacement during Read.
227 if tc.replacement != 0 {
228 r.setDeadlineLocked("initial deadline")
229 r.Deadline.Timeout = tc.replacement
230 }
231 _, err := r.Read(make([]byte, 1))
232 require.ErrorIs(t, err, terminalErr)
233 afterRead := w.snapshot()
234 if tc.wantClear {
235 require.True(t, afterRead[len(afterRead)-1].IsZero(), "must attempt clearing after Read")
236 }
237 r.HandlerDone()
238 deadlines := w.snapshot()
239 require.Len(t, deadlines, tc.wantCalls)
240 if tc.wantClear {
241 assert.True(t, deadlines[len(deadlines)-1].IsZero())
242 } else if tc.failAt != 1 {
243 assert.True(t, deadlines[0].Before(time.Now()), "the successfully installed deadline expired")
244 }
245 _, err = r.Read(make([]byte, 1))
246 require.ErrorIs(t, err, terminalErr)
247 r.HandlerDone()
248 assert.Equal(t, deadlines, w.snapshot(), "late reads and completion must leave deadlines alone")
249 })
250 }
251 }
252
253 func TestIdleTimeoutReaderTimeoutAfterOverride(t *testing.T) {
254 w := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
255 r := &IdleTimeoutReader{
256 ReadCloser: io.NopCloser(iotest.ErrReader(os.ErrDeadlineExceeded)),
257 Ctrl: http.NewResponseController(w),
258 Deadline: IdleDeadline{Timeout: time.Hour},
259 Logger: zap.NewNop(),
260 DrainDeadline: true,
261 }
262 restore := r.Override(-time.Second, 0)
263 _, err := r.Read(make([]byte, 1))
264 require.ErrorIs(t, err, os.ErrDeadlineExceeded)
265 deadlines := w.snapshot()
266 require.NotEmpty(t, deadlines)
267 expired := deadlines[0]
268 require.True(t, expired.Before(time.Now()))
269 assert.Equal(t, expired, deadlines[len(deadlines)-1])
270
271 // Unwinding a route-level timeout must not rearm the longer server timeout.
272 restore()
273 r.HandlerDone()
274 deadlines = w.snapshot()
275 assert.Equal(t, expired, deadlines[len(deadlines)-1])
276 }
277
278 func TestIdleTimeoutReaderHandlerDone(t *testing.T) {
279 body := &blockingReadCloser{
280 started: make(chan struct{}),
281 release: make(chan struct{}),
282 }
283 w := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
284 r := &IdleTimeoutReader{
285 ReadCloser: body,
286 Ctrl: http.NewResponseController(w),
287 Deadline: IdleDeadline{Timeout: time.Second},
288 Logger: zap.NewNop(),
289 }
290
291 readDone := make(chan struct{})
292 go func() {
293 defer close(readDone)
294 _, _ = r.Read(make([]byte, 1))
295 }()
296 <-body.started
297
298 r.HandlerDone()
299 deadlines := w.snapshot()
300 require.Len(t, deadlines, 1)
301 assert.False(t, deadlines[0].IsZero())
302
303 close(body.release)
304 <-readDone
305 _, _ = r.Read(make([]byte, 1))
306 r.HandlerDone()
307 assert.Len(t, w.snapshot(), len(deadlines))
308 }
309
310 func TestIdleTimeoutReaderHandlerDoneAfterResetFailure(t *testing.T) {
311 w := &readDeadlineRecorder{
312 ResponseRecorder: httptest.NewRecorder(),
313 failAt: 2,
314 }
315 r := &IdleTimeoutReader{
316 ReadCloser: io.NopCloser(strings.NewReader("body")),
317 Ctrl: http.NewResponseController(w),
318 Deadline: IdleDeadline{Timeout: time.Second},
319 Logger: zap.NewNop(),
320 }
321
322 _, _ = r.Read(make([]byte, 1))
323 _, _ = r.Read(make([]byte, 1))
324 r.HandlerDone()
325
326 deadlines := w.snapshot()
327 require.Len(t, deadlines, 2)
328 assert.False(t, deadlines[0].IsZero())
329 assert.False(t, deadlines[1].IsZero())
330 }
331
332 func TestIdleTimeoutReaderHandlerDoneRetriesTerminalClear(t *testing.T) {
333 w := &readDeadlineRecorder{
334 ResponseRecorder: httptest.NewRecorder(),
335 failAt: 3,
336 }
337 r := &IdleTimeoutReader{
338 ReadCloser: io.NopCloser(strings.NewReader("body")),
339 Ctrl: http.NewResponseController(w),
340 Deadline: IdleDeadline{Timeout: time.Second},
341 Logger: zap.NewNop(),
342 }
343
344 _, err := io.Copy(io.Discard, r)
345 require.NoError(t, err)
346 r.HandlerDone()
347
348 deadlines := w.snapshot()
349 require.Len(t, deadlines, 4)
350 assert.False(t, deadlines[0].IsZero())
351 assert.False(t, deadlines[1].IsZero())
352 assert.True(t, deadlines[2].IsZero())
353 assert.True(t, deadlines[3].IsZero())
354 }
355
356 func TestIdleTimeoutReaderHandlerDoneArmsDrain(t *testing.T) {
357 w := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
358 r := &IdleTimeoutReader{
359 ReadCloser: io.NopCloser(strings.NewReader("body")),
360 Ctrl: http.NewResponseController(w),
361 Deadline: IdleDeadline{Timeout: time.Second},
362 Logger: zap.NewNop(),
363 DrainDeadline: true,
364 }
365
366 r.HandlerDone()
367 deadlines := w.snapshot()
368 require.Len(t, deadlines, 1)
369 assert.False(t, deadlines[0].IsZero())
370 }
371
372 func TestIdleTimeoutReaderHandlerDoneArmsDrainAfterHandlerDeadline(t *testing.T) {
373 w := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
374 r := &IdleTimeoutReader{
375 ReadCloser: io.NopCloser(strings.NewReader("body")),
376 Ctrl: http.NewResponseController(w),
377 Deadline: IdleDeadline{Timeout: time.Second},
378 Logger: zap.NewNop(),
379 DrainDeadline: true,
380 }
381 handlerDeadline := time.Now().Add(time.Hour)
382 require.NoError(t, r.SetReadDeadline(handlerDeadline))
383
384 r.HandlerDone()
385 deadlines := w.snapshot()
386 require.Len(t, deadlines, 2)
387 assert.False(t, deadlines[1].IsZero())
388 assert.True(t, deadlines[1].Before(handlerDeadline))
389 }
390
391 func TestIdleTimeoutReaderHandlerDoneWithoutDrainDeadline(t *testing.T) {
392 w := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
393 r := &IdleTimeoutReader{
394 ReadCloser: io.NopCloser(strings.NewReader("body")),
395 Ctrl: http.NewResponseController(w),
396 Deadline: IdleDeadline{Timeout: time.Second},
397 Logger: zap.NewNop(),
398 }
399
400 r.HandlerDone()
401 assert.Empty(t, w.snapshot())
402 }
403
404 func TestIdleTimeoutReaderAfterHandlerReturnsHTTP2(t *testing.T) {
405 lateBody := make(chan io.ReadCloser, 1)
406 srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
407 wrapped := &IdleTimeoutReader{
408 ReadCloser: r.Body,
409 Ctrl: http.NewResponseController(w),
410 Deadline: IdleDeadline{Timeout: time.Second},
411 Logger: zap.NewNop(),
412 }
413 defer wrapped.HandlerDone()
414 lateBody <- wrapped
415 _, _ = io.WriteString(w, "ok")
416 }))
417 srv.EnableHTTP2 = true
418 srv.StartTLS()
419 defer srv.Close()
420
421 resp, err := srv.Client().Post(srv.URL, "text/plain", strings.NewReader("body"))
422 require.NoError(t, err)
423 responseBody, err := io.ReadAll(resp.Body)
424 require.NoError(t, err)
425 require.NoError(t, resp.Body.Close())
426 require.Equal(t, 2, resp.ProtoMajor)
427 require.Equal(t, "ok", string(responseBody))
428
429 readResult := make(chan error, 1)
430 go func() {
431 var result error
432 defer func() {
433 if recovered := recover(); recovered != nil {
434 result = fmt.Errorf("late request-body read panicked: %v", recovered)
435 }
436 readResult <- result
437 }()
438 _, _ = io.Copy(io.Discard, <-lateBody)
439 }()
440 select {
441 case err := <-readResult:
442 assert.NoError(t, err)
443 case <-time.After(time.Second):
444 t.Fatal("late request-body read did not return")
445 }
446 }
447
448 func TestIdleTimeoutReaderHandlerDonePreservesHTTP1Connection(t *testing.T) {
449 const idle = 100 * time.Millisecond
450
451 remoteAddresses := make(chan string, 2)
452 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
453 wrapped := &IdleTimeoutReader{
454 ReadCloser: r.Body,
455 Ctrl: http.NewResponseController(w),
456 Deadline: IdleDeadline{Timeout: idle},
457 Logger: zap.NewNop(),
458 DrainDeadline: r.ProtoMajor == 1 && r.ContentLength != 0,
459 }
460 defer wrapped.HandlerDone()
461 remoteAddresses <- r.RemoteAddr
462 if r.Method == http.MethodPost {
463 _, _ = wrapped.Read(make([]byte, 1))
464 }
465 w.WriteHeader(http.StatusNoContent)
466 }))
467 defer srv.Close()
468
469 transport := &http.Transport{MaxConnsPerHost: 1}
470 client := &http.Client{Transport: transport}
471 defer transport.CloseIdleConnections()
472
473 resp, err := client.Post(srv.URL, "text/plain", strings.NewReader("body"))
474 require.NoError(t, err)
475 require.NoError(t, resp.Body.Close())
476 require.Equal(t, http.StatusNoContent, resp.StatusCode)
477 firstAddress := <-remoteAddresses
478
479 time.Sleep(3 * idle)
480 resp, err = client.Get(srv.URL)
481 require.NoError(t, err)
482 require.NoError(t, resp.Body.Close())
483 require.Equal(t, http.StatusNoContent, resp.StatusCode)
484 assert.Equal(t, firstAddress, <-remoteAddresses)
485 }
486
487 func TestIdleTimeoutReaderDeadlineClearedAfterBodyEOF(t *testing.T) {
488 const idle = 150 * time.Millisecond
489
490 upstream := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
491 _, _ = io.Copy(io.Discard, r.Body)
492 }))
493 defer upstream.Close()
494
495 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
496 wrapped := &IdleTimeoutReader{
497 ReadCloser: r.Body,
498 Ctrl: http.NewResponseController(w),
499 Deadline: IdleDeadline{Timeout: idle},
500 Logger: zap.NewNop(),
501 }
502 defer wrapped.HandlerDone()
503
504 upReq, err := http.NewRequestWithContext(r.Context(), r.Method, upstream.URL, wrapped)
505 if err != nil {
506 t.Error(err)
507 return
508 }
509 upReq.ContentLength = r.ContentLength
510 resp, err := http.DefaultClient.Do(upReq)
511 if err != nil {
512 t.Error(err)
513 return
514 }
515 _ = resp.Body.Close()
516
517 select {
518 case <-r.Context().Done():
519 w.WriteHeader(http.StatusTeapot)
520 case <-time.After(3 * idle):
521 w.WriteHeader(http.StatusOK)
522 }
523 }))
524 defer srv.Close()
525
526 resp, err := http.Post(srv.URL, "application/octet-stream", strings.NewReader("{}"))
527 require.NoError(t, err)
528 defer resp.Body.Close()
529 assert.Equal(t, http.StatusOK, resp.StatusCode)
530 }
531
532 func TestIdleTimeoutReader_HardDeadlineCapsIdleReset(t *testing.T) {
533 const idle = 500 * time.Millisecond
534 const hard = 200 * time.Millisecond
535
536 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
537 wrapped := &IdleTimeoutReader{
538 ReadCloser: r.Body,
539 Ctrl: http.NewResponseController(w),
540 Deadline: IdleDeadline{
541 Timeout: idle,
542 HardDeadline: time.Now().Add(hard),
543 },
544 Logger: zap.NewNop(),
545 }
546 _, err := io.Copy(io.Discard, wrapped)
547 if err != nil {
548 http.Error(w, err.Error(), http.StatusRequestTimeout)
549 return
550 }
551 w.WriteHeader(http.StatusOK)
552 }))
553 defer srv.Close()
554
555 // each gap is under the idle timeout, so the connection is never
556 // idle, but the cumulative transfer time exceeds hardDeadline: an
557 // explicit hard ceiling must still cut it off despite the ongoing
558 // idle-reset activity
559 body := &pacedReader{delay: hard, chunkSize: 8, chunkCount: 8}
560 resp, err := http.Post(srv.URL, "application/octet-stream", body)
561 if err != nil {
562 // the connection may also be reset outright, which is fine
563 return
564 }
565 defer resp.Body.Close()
566 assert.NotEqual(t, http.StatusOK, resp.StatusCode)
567 }
568
569 func TestIdleTimeoutReader_MinRateCatchesTrickle(t *testing.T) {
570 const idle = 250 * time.Millisecond
571 const chunkDelay = 150 * time.Millisecond
572
573 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
574 wrapped := &IdleTimeoutReader{
575 ReadCloser: r.Body,
576 Ctrl: http.NewResponseController(w),
577 Deadline: IdleDeadline{
578 Start: time.Now(),
579 Timeout: idle,
580 // a huge min rate means even a full-size read call
581 // earns virtually no extra credit, so a byte-sized
582 // trickle can't keep the connection alive past idle
583 MinRate: 100_000_000,
584 },
585 Logger: zap.NewNop(),
586 }
587 _, err := io.Copy(io.Discard, wrapped)
588 if err != nil {
589 http.Error(w, err.Error(), http.StatusRequestTimeout)
590 return
591 }
592 w.WriteHeader(http.StatusOK)
593 }))
594 defer srv.Close()
595
596 // each individual gap (150ms) is under the idle window (250ms), so
597 // pure idle-reset alone would never trip; MinRate must still catch
598 // this trickle since it never earns meaningful credit
599 body := &pacedReader{delay: chunkDelay, chunkSize: 1, chunkCount: 6}
600 resp, err := http.Post(srv.URL, "application/octet-stream", body)
601 if err != nil {
602 return
603 }
604 defer resp.Body.Close()
605 assert.NotEqual(t, http.StatusOK, resp.StatusCode)
606 }
607
608 func TestIdleTimeoutReader_MinRateAllowsSustainedRate(t *testing.T) {
609 const idle = 250 * time.Millisecond
610 const chunkDelay = 100 * time.Millisecond
611 const minRate = 1000 // bytes/second
612
613 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
614 wrapped := &IdleTimeoutReader{
615 ReadCloser: r.Body,
616 Ctrl: http.NewResponseController(w),
617 Deadline: IdleDeadline{
618 Start: time.Now(),
619 Timeout: idle,
620 MinRate: minRate,
621 },
622 Logger: zap.NewNop(),
623 }
624 _, err := io.Copy(io.Discard, wrapped)
625 if err != nil {
626 http.Error(w, err.Error(), http.StatusRequestTimeout)
627 return
628 }
629 w.WriteHeader(http.StatusOK)
630 }))
631 defer srv.Close()
632
633 // 200 bytes every 100ms is 2000 bytes/second, comfortably above the
634 // 1000 bytes/second minRate, so credit earned per chunk (200ms)
635 // outpaces the real time elapsed per chunk (100ms): this transfer
636 // must complete despite exceeding the idle window cumulatively
637 body := &pacedReader{delay: chunkDelay, chunkSize: 200, chunkCount: 8}
638 resp, err := http.Post(srv.URL, "application/octet-stream", body)
639 require.NoError(t, err)
640 defer resp.Body.Close()
641 assert.Equal(t, http.StatusOK, resp.StatusCode)
642 }
643
644 func TestIdleTimeoutWriter(t *testing.T) {
645 const timeout = 150 * time.Millisecond
646
647 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
648 wrapped := &IdleTimeoutWriter{
649 ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: w},
650 Ctrl: http.NewResponseController(w),
651 Deadline: IdleDeadline{Timeout: timeout},
652 Logger: zap.NewNop(),
653 }
654 flusher, _ := wrapped.ResponseWriterWrapper.ResponseWriter.(http.Flusher)
655 for range 8 {
656 time.Sleep(timeout / 4)
657 _, err := wrapped.Write(make([]byte, 8))
658 if err != nil {
659 t.Errorf("unexpected write error: %v", err)
660 return
661 }
662 if flusher != nil {
663 flusher.Flush()
664 }
665 }
666 }))
667 defer srv.Close()
668
669 // each write gap is well under timeout, but the cumulative streaming
670 // time is well over it; a hard deadline would kill this response
671 resp, err := http.Get(srv.URL)
672 require.NoError(t, err)
673 defer resp.Body.Close()
674
675 n, err := io.Copy(io.Discard, resp.Body)
676 require.NoError(t, err)
677 assert.EqualValues(t, 64, n)
678 }
679
680 func TestIdleTimeoutWriter_HardDeadlineCapsIdleReset(t *testing.T) {
681 const idle = 500 * time.Millisecond
682 const hard = 200 * time.Millisecond
683
684 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
685 wrapped := &IdleTimeoutWriter{
686 ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: w},
687 Ctrl: http.NewResponseController(w),
688 Deadline: IdleDeadline{
689 Timeout: idle,
690 HardDeadline: time.Now().Add(hard),
691 },
692 Logger: zap.NewNop(),
693 }
694 flusher, _ := wrapped.ResponseWriterWrapper.ResponseWriter.(http.Flusher)
695 for range 8 {
696 time.Sleep(hard)
697 if _, err := wrapped.Write(make([]byte, 8)); err != nil {
698 return
699 }
700 if flusher != nil {
701 flusher.Flush()
702 }
703 }
704 }))
705 defer srv.Close()
706
707 // each write gap is under the idle timeout, so the connection is
708 // never idle, but the cumulative streaming time exceeds
709 // hardDeadline: an explicit hard ceiling must still cut it off,
710 // whether that surfaces as a failed request, a truncated body, or
711 // both
712 resp, err := http.Get(srv.URL)
713 if err != nil {
714 return
715 }
716 defer resp.Body.Close()
717
718 n, err := io.Copy(io.Discard, resp.Body)
719 if err == nil {
720 assert.Less(t, n, int64(64))
721 }
722 }
723
724 // TestIdleTimeoutWriter_Streaming covers #8118: on HTTP/2 the write
725 // deadline is a timer that resets the stream when it fires, so a
726 // deadline left armed between writes kills a stream that merely pauses.
727 func TestIdleTimeoutWriter_Streaming(t *testing.T) {
728 const idle = 100 * time.Millisecond
729 const interval = 250 * time.Millisecond
730 const count = 3
731
732 srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
733 wrapped := &IdleTimeoutWriter{
734 ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: w},
735 Ctrl: http.NewResponseController(w),
736 Deadline: IdleDeadline{Timeout: idle},
737 Logger: zap.NewNop(),
738 ClearBetweenWrites: r.ProtoMajor == 2,
739 }
740 defer wrapped.HandlerDone()
741 wrapped.Header().Set("Content-Type", "text/event-stream")
742 rc := http.NewResponseController(wrapped)
743
744 for i := range count {
745 time.Sleep(interval)
746 if _, err := fmt.Fprintf(wrapped, "data: %d\n\n", i); err != nil {
747 t.Logf("write error: %v", err)
748 return
749 }
750 if err := rc.Flush(); err != nil {
751 t.Logf("flush error: %v", err)
752 return
753 }
754 }
755 }))
756 srv.EnableHTTP2 = true
757 srv.StartTLS()
758 defer srv.Close()
759
760 resp, err := srv.Client().Get(srv.URL)
761 require.NoError(t, err)
762 defer resp.Body.Close()
763 require.Equal(t, 2, resp.ProtoMajor)
764
765 scanner := bufio.NewScanner(resp.Body)
766 var linesRead int
767 for scanner.Scan() {
768 linesRead++
769 }
770 require.NoError(t, scanner.Err())
771 require.Equal(t, 2*count, linesRead)
772 }
773
774 // writeDeadlineRecorder records every write deadline set on it.
775 type writeDeadlineRecorder struct {
776 *httptest.ResponseRecorder
777 deadlines []time.Time
778 }
779
780 func (w *writeDeadlineRecorder) SetWriteDeadline(deadline time.Time) error {
781 w.deadlines = append(w.deadlines, deadline)
782 return nil
783 }
784
785 func TestIdleTimeoutWriter_ClearBetweenWrites(t *testing.T) {
786 const idle = time.Hour
787 hard := time.Now().Add(2 * time.Hour)
788
789 // the values recorded for each SetWriteDeadline call
790 const (
791 idleSet = "idle"
792 zeroSet = "zero"
793 hardSet = "hard"
794 otherSet = "other"
795 )
796
797 for i, tc := range []struct {
798 name string
799 clear bool
800 hard bool
801 idle time.Duration
802 ops func(w *IdleTimeoutWriter) error
803 expected []string
804 }{
805 {
806 name: "write keeps the deadline when not clearing",
807 ops: func(w *IdleTimeoutWriter) error { _, err := w.Write([]byte("x")); return err },
808 expected: []string{idleSet},
809 },
810 {
811 name: "write clears the deadline",
812 clear: true,
813 ops: func(w *IdleTimeoutWriter) error { _, err := w.Write([]byte("x")); return err },
814 expected: []string{idleSet, zeroSet},
815 },
816 {
817 name: "write puts back the hard deadline",
818 clear: true,
819 hard: true,
820 ops: func(w *IdleTimeoutWriter) error { _, err := w.Write([]byte("x")); return err },
821 expected: []string{idleSet, hardSet},
822 },
823 {
824 name: "empty write leaves the deadline alone",
825 clear: true,
826 hard: true,
827 ops: func(w *IdleTimeoutWriter) error { _, err := w.Write(nil); return err },
828 expected: nil,
829 },
830 {
831 name: "read from clears the deadline",
832 clear: true,
833 ops: func(w *IdleTimeoutWriter) error { _, err := w.ReadFrom(bytes.NewReader([]byte("x"))); return err },
834 expected: []string{idleSet, zeroSet},
835 },
836 {
837 name: "flush is bounded by the idle deadline",
838 ops: func(w *IdleTimeoutWriter) error { return w.FlushError() },
839 expected: []string{idleSet},
840 },
841 {
842 name: "flush clears the deadline",
843 clear: true,
844 ops: func(w *IdleTimeoutWriter) error { return w.FlushError() },
845 expected: []string{idleSet, zeroSet},
846 },
847 {
848 name: "handler done arms a deadline for unflushed writes",
849 clear: true,
850 ops: func(w *IdleTimeoutWriter) error {
851 _, err := w.Write([]byte("x"))
852 w.HandlerDone()
853 return err
854 },
855 expected: []string{idleSet, zeroSet, idleSet},
856 },
857 {
858 name: "handler done does nothing after a flush",
859 clear: true,
860 ops: func(w *IdleTimeoutWriter) error {
861 if _, err := w.Write([]byte("x")); err != nil {
862 return err
863 }
864 err := w.FlushError()
865 w.HandlerDone()
866 return err
867 },
868 expected: []string{idleSet, zeroSet, idleSet, zeroSet},
869 },
870 {
871 name: "hard deadline is restored for each operation",
872 clear: true,
873 hard: true,
874 idle: 3 * time.Hour,
875 ops: func(w *IdleTimeoutWriter) error {
876 if _, err := w.Write([]byte("x")); err != nil {
877 return err
878 }
879 if _, err := w.Write([]byte("x")); err != nil {
880 return err
881 }
882 w.HandlerDone()
883 return nil
884 },
885 expected: []string{hardSet, hardSet, hardSet, hardSet, hardSet},
886 },
887 {
888 name: "handler done does nothing when not clearing",
889 ops: func(w *IdleTimeoutWriter) error {
890 _, err := w.Write([]byte("x"))
891 w.HandlerDone()
892 return err
893 },
894 expected: []string{idleSet},
895 },
896 } {
897 timeout := idle
898 if tc.idle > 0 {
899 timeout = tc.idle
900 }
901 rec := &writeDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
902 w := &IdleTimeoutWriter{
903 ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: rec},
904 Ctrl: http.NewResponseController(rec),
905 Deadline: IdleDeadline{Timeout: timeout},
906 Logger: zap.NewNop(),
907 ClearBetweenWrites: tc.clear,
908 }
909 if tc.hard {
910 w.Deadline.HardDeadline = hard
911 }
912
913 before := time.Now()
914 if err := tc.ops(w); err != nil {
915 t.Errorf("Test %d (%s): unexpected error: %v", i, tc.name, err)
916 }
917 after := time.Now()
918
919 var actual []string
920 for _, d := range rec.deadlines {
921 switch {
922 case d.IsZero():
923 actual = append(actual, zeroSet)
924 case d.Equal(hard):
925 actual = append(actual, hardSet)
926 case !d.Before(before.Add(idle)) && !d.After(after.Add(idle)):
927 actual = append(actual, idleSet)
928 default:
929 actual = append(actual, otherSet)
930 }
931 }
932 assert.Equal(t, tc.expected, actual, "Test %d (%s)", i, tc.name)
933 }
934 }
935
936 func TestIdleTimeoutWriter_HandlerDeadline(t *testing.T) {
937 const idle = time.Hour
938 now := time.Now()
939 earlier := now.Add(30 * time.Minute)
940 later := now.Add(2 * time.Hour)
941 hard := now.Add(20 * time.Minute)
942
943 // the values recorded for each SetWriteDeadline call
944 const (
945 idleSet = "idle"
946 zeroSet = "zero"
947 earlierSet = "earlier"
948 laterSet = "later"
949 hardSet = "hard"
950 otherSet = "other"
951 )
952
953 for i, tc := range []struct {
954 name string
955 clear bool
956 hard bool
957 deadlines []time.Time
958 expected []string
959 }{
960 {
961 name: "earlier handler deadline caps the idle reset",
962 deadlines: []time.Time{earlier},
963 expected: []string{earlierSet, earlierSet},
964 },
965 {
966 name: "clearing puts back the handler deadline",
967 clear: true,
968 deadlines: []time.Time{earlier},
969 expected: []string{earlierSet, earlierSet, earlierSet},
970 },
971 {
972 name: "later handler deadline doesn't loosen the idle timeout",
973 deadlines: []time.Time{later},
974 expected: []string{laterSet, idleSet},
975 },
976 {
977 name: "clearing puts back a later handler deadline",
978 clear: true,
979 deadlines: []time.Time{later},
980 expected: []string{laterSet, idleSet, laterSet},
981 },
982 {
983 name: "zero handler deadline removes the ceiling",
984 deadlines: []time.Time{earlier, {}},
985 expected: []string{earlierSet, zeroSet, idleSet},
986 },
987 {
988 name: "earlier hard deadline stays the ceiling",
989 clear: true,
990 hard: true,
991 deadlines: []time.Time{earlier},
992 expected: []string{earlierSet, hardSet, hardSet},
993 },
994 } {
995 rec := &writeDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
996 w := &IdleTimeoutWriter{
997 ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: rec},
998 Ctrl: http.NewResponseController(rec),
999 Deadline: IdleDeadline{Timeout: idle},
1000 Logger: zap.NewNop(),
1001 ClearBetweenWrites: tc.clear,
1002 }
1003 if tc.hard {
1004 w.Deadline.HardDeadline = hard
1005 }
1006
1007 // handlers usually reach the writer through other wrappers
1008 ctrl := http.NewResponseController(&ResponseWriterWrapper{ResponseWriter: w})
1009 for _, d := range tc.deadlines {
1010 require.NoError(t, ctrl.SetWriteDeadline(d))
1011 }
1012
1013 before := time.Now()
1014 _, err := w.Write([]byte("x"))
1015 require.NoError(t, err)
1016 after := time.Now()
1017
1018 var actual []string
1019 for _, d := range rec.deadlines {
1020 switch {
1021 case d.IsZero():
1022 actual = append(actual, zeroSet)
1023 case d.Equal(earlier):
1024 actual = append(actual, earlierSet)
1025 case d.Equal(later):
1026 actual = append(actual, laterSet)
1027 case d.Equal(hard):
1028 actual = append(actual, hardSet)
1029 case !d.Before(before.Add(idle)) && !d.After(after.Add(idle)):
1030 actual = append(actual, idleSet)
1031 default:
1032 actual = append(actual, otherSet)
1033 }
1034 }
1035 assert.Equal(t, tc.expected, actual, "Test %d (%s)", i, tc.name)
1036 }
1037 }
1038
1039 // readFromCounter is a fake http.ResponseWriter that records how many
1040 // times ReadFrom was called on it and the size of the largest call, so
1041 // tests can assert on chunking behavior deterministically instead of
1042 // depending on real TCP timing.
1043 type readFromCounter struct {
1044 *httptest.ResponseRecorder
1045 calls int
1046 maxCall int
1047 total int64
1048 writeCalls int
1049 maxWriteCall int
1050 }
1051
1052 func (c *readFromCounter) SetWriteDeadline(time.Time) error { return nil }
1053
1054 func (c *readFromCounter) ReadFrom(r io.Reader) (int64, error) {
1055 c.calls++
1056 n, err := io.Copy(io.Discard, r)
1057 c.total += n
1058 if int(n) > c.maxCall {
1059 c.maxCall = int(n)
1060 }
1061 return n, err
1062 }
1063
1064 func (c *readFromCounter) Write(p []byte) (int, error) {
1065 c.writeCalls++
1066 if len(p) > c.maxWriteCall {
1067 c.maxWriteCall = len(p)
1068 }
1069 return c.ResponseRecorder.Write(p)
1070 }
1071
1072 func TestIdleTimeoutWriter_ReadFromChunksLargeTransfer(t *testing.T) {
1073 const size = DefaultMaxWriteChunk*3 + 100 // 3 full chunks plus a partial tail
1074
1075 counter := &readFromCounter{ResponseRecorder: httptest.NewRecorder()}
1076 wrapped := &IdleTimeoutWriter{
1077 ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: counter},
1078 Ctrl: http.NewResponseController(counter),
1079 Deadline: IdleDeadline{Timeout: time.Second},
1080 Logger: zap.NewNop(),
1081 }
1082
1083 n, err := wrapped.ReadFrom(bytes.NewReader(make([]byte, size)))
1084 require.NoError(t, err)
1085 assert.EqualValues(t, size, n)
1086 assert.EqualValues(t, size, counter.total)
1087 assert.LessOrEqual(t, counter.maxCall, DefaultMaxWriteChunk,
1088 "no single underlying ReadFrom call should cover more than DefaultMaxWriteChunk bytes, "+
1089 "since SetWriteDeadline bounds the whole call it precedes, not just a stall within it")
1090 assert.Equal(t, 4, counter.calls, "expected 3 full chunks plus a partial tail chunk")
1091 }
1092
1093 func TestIdleTimeoutWriter_WriteChunksLargePayload(t *testing.T) {
1094 const size = DefaultMaxWriteChunk*2 + 1
1095
1096 counter := &readFromCounter{ResponseRecorder: httptest.NewRecorder()}
1097 wrapped := &IdleTimeoutWriter{
1098 ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: counter},
1099 Ctrl: http.NewResponseController(counter),
1100 Deadline: IdleDeadline{Timeout: time.Second},
1101 Logger: zap.NewNop(),
1102 }
1103
1104 n, err := wrapped.Write(make([]byte, size))
1105 require.NoError(t, err)
1106 assert.EqualValues(t, size, n)
1107 assert.EqualValues(t, size, counter.ResponseRecorder.Body.Len())
1108 assert.LessOrEqual(t, counter.maxWriteCall, DefaultMaxWriteChunk,
1109 "no single underlying Write call should cover more than DefaultMaxWriteChunk bytes")
1110 assert.Equal(t, 3, counter.writeCalls, "expected 2 full chunks plus a 1-byte tail chunk")
1111 }
1112
1113 func TestIdleTimeoutWriter_MaxChunkOverride(t *testing.T) {
1114 const size = 1000
1115 const maxChunk = 100
1116
1117 counter := &readFromCounter{ResponseRecorder: httptest.NewRecorder()}
1118 wrapped := &IdleTimeoutWriter{
1119 ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: counter},
1120 Ctrl: http.NewResponseController(counter),
1121 Deadline: IdleDeadline{Timeout: time.Second},
1122 MaxChunk: maxChunk,
1123 Logger: zap.NewNop(),
1124 }
1125
1126 n, err := wrapped.Write(make([]byte, size))
1127 require.NoError(t, err)
1128 assert.EqualValues(t, size, n)
1129 assert.LessOrEqual(t, counter.maxWriteCall, maxChunk,
1130 "a configured MaxChunk should override DefaultMaxWriteChunk")
1131 assert.Equal(t, size/maxChunk, counter.writeCalls)
1132 }
1133
1134 func TestIdleDeadlineOverrideRestore(t *testing.T) {
1135 start := time.Now().Add(-time.Minute)
1136 hard := time.Now().Add(time.Hour)
1137 d := IdleDeadline{Start: start, Timeout: time.Second, MinRate: 10, HardDeadline: hard, transferred: 100}
1138
1139 o := d.override(time.Minute, 0)
1140 assert.Equal(t, time.Minute, d.Timeout)
1141 assert.Zero(t, d.MinRate)
1142 assert.Zero(t, d.transferred)
1143 assert.Equal(t, hard, d.HardDeadline)
1144 assert.True(t, d.Start.After(start))
1145
1146 // pretend the override lasted 10s and transferred some bytes; the
1147 // previous window is suspended for that long and ignores them
1148 o.at = o.at.Add(-10 * time.Second)
1149 d.transferred += 50
1150 before := time.Now()
1151 d.restore(o)
1152 after := time.Now()
1153
1154 assert.Equal(t, time.Second, d.Timeout)
1155 assert.EqualValues(t, 10, d.MinRate)
1156 assert.EqualValues(t, 100, d.transferred)
1157 assert.Equal(t, hard, d.HardDeadline)
1158 assert.WithinRange(t, d.Start,
1159 start.Add(10*time.Second).Add(before.Sub(o.at.Add(10*time.Second))),
1160 start.Add(10*time.Second).Add(after.Sub(o.at.Add(10*time.Second))))
1161 }
1162
1163 // TestIdleTimeoutReaderOverrideSuspendsMinRate checks that time spent
1164 // under nested overrides doesn't count against the minimum-rate windows
1165 // that are restored afterwards.
1166 func TestIdleTimeoutReaderOverrideSuspendsMinRate(t *testing.T) {
1167 const timeout = 200 * time.Millisecond
1168 const overridden = 150 * time.Millisecond
1169
1170 w := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
1171 r := &IdleTimeoutReader{
1172 ReadCloser: io.NopCloser(strings.NewReader("body")),
1173 Ctrl: http.NewResponseController(w),
1174 Deadline: IdleDeadline{Start: time.Now(), Timeout: timeout, MinRate: 1 << 30},
1175 Logger: zap.NewNop(),
1176 }
1177
1178 // an outer scope with its own minimum rate, then an inner one; each
1179 // is overridden for longer than the outer timeout in total
1180 restoreOuter := r.Override(timeout, 1<<30)
1181 time.Sleep(overridden)
1182 restoreInner := r.Override(time.Hour, 0)
1183 time.Sleep(2 * overridden)
1184 restoreInner()
1185 _, err := r.Read(make([]byte, 1))
1186 require.NoError(t, err)
1187 deadlines := w.snapshot()
1188 require.NotEmpty(t, deadlines)
1189 assert.True(t, deadlines[len(deadlines)-1].After(time.Now()),
1190 "the restored outer scope's deadline should not have expired while overridden")
1191 restoreOuter()
1192
1193 _, err = r.Read(make([]byte, 1))
1194 require.NoError(t, err)
1195 deadlines = w.snapshot()
1196 assert.True(t, deadlines[len(deadlines)-1].After(time.Now()),
1197 "the restored server-wide deadline should not have expired while overridden")
1198 }
1199
1200 // TestIdleTimeoutReaderPausedHandlerHTTP2 checks that a handler that
1201 // stops reading the body for longer than the idle timeout doesn't lose
1202 // it on HTTP/2, where the read deadline is a timer that fails the body
1203 // when it fires, even with no read in flight. The client isn't stalled:
1204 // it's held back by flow control while the handler isn't reading.
1205 func TestIdleTimeoutReaderPausedHandlerHTTP2(t *testing.T) {
1206 const startupIdle = 5 * time.Second
1207 const idle = 100 * time.Millisecond
1208 const pause = 300 * time.Millisecond
1209 const size = 4 << 20 // more than the HTTP/2 flow control window
1210
1211 srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1212 reader := &IdleTimeoutReader{
1213 ReadCloser: r.Body,
1214 Ctrl: http.NewResponseController(w),
1215 Deadline: IdleDeadline{Start: time.Now(), Timeout: startupIdle},
1216 Logger: zap.NewNop(),
1217 ClearBetweenReads: r.ProtoMajor == 2,
1218 }
1219 defer reader.HandlerDone()
1220
1221 if _, err := io.ReadFull(reader, make([]byte, 8)); err != nil {
1222 http.Error(w, err.Error(), http.StatusBadRequest)
1223 return
1224 }
1225 // Override re-arms the short deadline if the first read failed to release it.
1226 restore := reader.Override(idle, 0)
1227 defer restore()
1228 time.Sleep(pause)
1229 n, err := io.Copy(io.Discard, reader)
1230 if err != nil {
1231 http.Error(w, err.Error(), http.StatusRequestTimeout)
1232 return
1233 }
1234 _, _ = fmt.Fprint(w, n+8)
1235 }))
1236 srv.EnableHTTP2 = true
1237 srv.StartTLS()
1238 defer srv.Close()
1239
1240 resp, err := srv.Client().Post(srv.URL, "application/octet-stream", bytes.NewReader(make([]byte, size)))
1241 require.NoError(t, err)
1242 defer resp.Body.Close()
1243 require.Equal(t, 2, resp.ProtoMajor)
1244 body, err := io.ReadAll(resp.Body)
1245 require.NoError(t, err)
1246 assert.Equal(t, http.StatusOK, resp.StatusCode, string(body))
1247 assert.Equal(t, fmt.Sprint(size), string(body))
1248 }
1249
1250 // TestIdleTimeoutReaderStalledClientHTTP2 checks that clearing the
1251 // deadline between reads still aborts a read from a client that stops
1252 // sending.
1253 func TestIdleTimeoutReaderStalledClientHTTP2(t *testing.T) {
1254 const idle = 100 * time.Millisecond
1255
1256 readErr := make(chan error, 1)
1257 srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1258 reader := &IdleTimeoutReader{
1259 ReadCloser: r.Body,
1260 Ctrl: http.NewResponseController(w),
1261 Deadline: IdleDeadline{Start: time.Now(), Timeout: idle},
1262 Logger: zap.NewNop(),
1263 ClearBetweenReads: r.ProtoMajor == 2,
1264 }
1265 defer reader.HandlerDone()
1266 _, err := io.Copy(io.Discard, reader)
1267 readErr <- err
1268 }))
1269 srv.EnableHTTP2 = true
1270 srv.StartTLS()
1271 defer srv.Close()
1272
1273 pr, pw := io.Pipe()
1274 defer pw.Close()
1275 go func() { _, _ = pw.Write(make([]byte, 8)) }() // then stall
1276 go func() {
1277 resp, err := srv.Client().Post(srv.URL, "application/octet-stream", pr)
1278 if err == nil {
1279 resp.Body.Close()
1280 }
1281 }()
1282
1283 select {
1284 case err := <-readErr:
1285 assert.ErrorIs(t, err, os.ErrDeadlineExceeded)
1286 case <-time.After(5 * time.Second):
1287 t.Fatal("read from a stalled client was not aborted")
1288 }
1289 }
1290
1291 func TestIdleTimeoutReaderClearBetweenReads(t *testing.T) {
1292 const idle = time.Hour
1293 hard := time.Now().Add(2 * time.Hour)
1294
1295 for i, tc := range []struct {
1296 clear bool
1297 hard bool
1298 expected []string
1299 }{
1300 // a read of "ab", then one returning io.EOF
1301 {clear: false, expected: []string{"idle", "idle", "zero"}},
1302 {clear: true, expected: []string{"idle", "zero", "idle", "zero"}},
1303 {clear: true, hard: true, expected: []string{"idle", "hard", "idle", "zero"}},
1304 } {
1305 w := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
1306 r := &IdleTimeoutReader{
1307 ReadCloser: io.NopCloser(strings.NewReader("ab")),
1308 Ctrl: http.NewResponseController(w),
1309 Deadline: IdleDeadline{Timeout: idle},
1310 Logger: zap.NewNop(),
1311 ClearBetweenReads: tc.clear,
1312 }
1313 if tc.hard {
1314 r.Deadline.HardDeadline = hard
1315 }
1316
1317 before := time.Now()
1318 _, err := io.Copy(io.Discard, r)
1319 after := time.Now()
1320 require.NoError(t, err)
1321
1322 var actual []string
1323 for _, d := range w.snapshot() {
1324 switch {
1325 case d.IsZero():
1326 actual = append(actual, "zero")
1327 case d.Equal(hard):
1328 actual = append(actual, "hard")
1329 case !d.Before(before.Add(idle)) && !d.After(after.Add(idle)):
1330 actual = append(actual, "idle")
1331 default:
1332 actual = append(actual, "other")
1333 }
1334 }
1335 assert.Equal(t, tc.expected, actual, "Test %d", i)
1336 }
1337 }
1338
1339 func TestIdleTimeoutReader_HandlerDeadline(t *testing.T) {
1340 const idle = time.Hour
1341 now := time.Now()
1342 earlier := now.Add(30 * time.Minute)
1343 later := now.Add(2 * time.Hour)
1344 hard := now.Add(20 * time.Minute)
1345
1346 // the values recorded for each SetReadDeadline call
1347 const (
1348 idleSet = "idle"
1349 zeroSet = "zero"
1350 earlierSet = "earlier"
1351 laterSet = "later"
1352 hardSet = "hard"
1353 otherSet = "other"
1354 )
1355
1356 for i, tc := range []struct {
1357 name string
1358 clear bool
1359 hard bool
1360 deadlines []time.Time
1361 expected []string
1362 }{
1363 {
1364 name: "earlier handler deadline caps the idle reset",
1365 deadlines: []time.Time{earlier},
1366 expected: []string{earlierSet, earlierSet, earlierSet, zeroSet},
1367 },
1368 {
1369 name: "clearing puts back the handler deadline",
1370 clear: true,
1371 deadlines: []time.Time{earlier},
1372 expected: []string{earlierSet, earlierSet, earlierSet, earlierSet, zeroSet},
1373 },
1374 {
1375 name: "later handler deadline doesn't loosen the idle timeout",
1376 deadlines: []time.Time{later},
1377 expected: []string{laterSet, idleSet, idleSet, zeroSet},
1378 },
1379 {
1380 name: "clearing puts back a later handler deadline",
1381 clear: true,
1382 deadlines: []time.Time{later},
1383 expected: []string{laterSet, idleSet, laterSet, idleSet, zeroSet},
1384 },
1385 {
1386 name: "zero handler deadline removes the ceiling",
1387 deadlines: []time.Time{earlier, {}},
1388 expected: []string{earlierSet, zeroSet, idleSet, idleSet, zeroSet},
1389 },
1390 {
1391 name: "earlier hard deadline stays the ceiling",
1392 clear: true,
1393 hard: true,
1394 deadlines: []time.Time{earlier},
1395 expected: []string{hardSet, hardSet, hardSet, hardSet, zeroSet},
1396 },
1397 {
1398 name: "later handler deadline doesn't lift the hard deadline",
1399 hard: true,
1400 deadlines: []time.Time{later},
1401 expected: []string{hardSet, hardSet, hardSet, zeroSet},
1402 },
1403 {
1404 name: "zero handler deadline keeps the hard deadline",
1405 hard: true,
1406 deadlines: []time.Time{{}},
1407 expected: []string{hardSet, hardSet, hardSet, zeroSet},
1408 },
1409 } {
1410 rec := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
1411 r := &IdleTimeoutReader{
1412 ReadCloser: io.NopCloser(strings.NewReader("ab")),
1413 Ctrl: http.NewResponseController(rec),
1414 Deadline: IdleDeadline{Timeout: idle},
1415 Logger: zap.NewNop(),
1416 ClearBetweenReads: tc.clear,
1417 }
1418 if tc.hard {
1419 r.Deadline.HardDeadline = hard
1420 }
1421
1422 // handlers usually reach the reader through other wrappers
1423 w := &readDeadlineWriter{ResponseWriterWrapper: ResponseWriterWrapper{ResponseWriter: rec}, reader: r}
1424 ctrl := http.NewResponseController(&ResponseWriterWrapper{ResponseWriter: w})
1425 for _, d := range tc.deadlines {
1426 require.NoError(t, ctrl.SetReadDeadline(d))
1427 }
1428
1429 before := time.Now()
1430 _, err := io.Copy(io.Discard, r)
1431 require.NoError(t, err)
1432 after := time.Now()
1433
1434 var actual []string
1435 for _, d := range rec.snapshot() {
1436 switch {
1437 case d.IsZero():
1438 actual = append(actual, zeroSet)
1439 case d.Equal(earlier):
1440 actual = append(actual, earlierSet)
1441 case d.Equal(later):
1442 actual = append(actual, laterSet)
1443 case d.Equal(hard):
1444 actual = append(actual, hardSet)
1445 case !d.Before(before.Add(idle)) && !d.After(after.Add(idle)):
1446 actual = append(actual, idleSet)
1447 default:
1448 actual = append(actual, otherSet)
1449 }
1450 }
1451 assert.Equal(t, tc.expected, actual, "Test %d (%s)", i, tc.name)
1452 }
1453 }
1454
1455 // TestIdleTimeoutReaderHandlerDeadlineDuringRead checks that a handler
1456 // can bring the idle deadline of a blocked read forward but can't
1457 // remove it or push it back.
1458 func TestIdleTimeoutReaderHandlerDeadlineDuringRead(t *testing.T) {
1459 earlier := time.Now().Add(time.Minute)
1460 for i, tc := range []struct {
1461 name string
1462 deadline time.Time
1463 expectHandler bool
1464 }{
1465 {name: "zero", deadline: time.Time{}},
1466 {name: "later", deadline: time.Now().Add(2 * time.Hour)},
1467 {name: "earlier", deadline: earlier, expectHandler: true},
1468 } {
1469 body := &blockingReadCloser{started: make(chan struct{}), release: make(chan struct{})}
1470 w := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
1471 r := &IdleTimeoutReader{
1472 ReadCloser: body,
1473 Ctrl: http.NewResponseController(w),
1474 Deadline: IdleDeadline{Timeout: time.Hour},
1475 Logger: zap.NewNop(),
1476 }
1477
1478 readDone := make(chan struct{})
1479 go func() {
1480 defer close(readDone)
1481 _, _ = r.Read(make([]byte, 1))
1482 }()
1483 <-body.started
1484
1485 require.NoError(t, r.SetReadDeadline(tc.deadline))
1486 deadlines := w.snapshot()
1487 require.Len(t, deadlines, 2, "Test %d (%s)", i, tc.name)
1488 expected := deadlines[0]
1489 if tc.expectHandler {
1490 expected = earlier
1491 }
1492 assert.Equal(t, expected, deadlines[1], "Test %d (%s)", i, tc.name)
1493
1494 close(body.release)
1495 <-readDone
1496 deadlines = w.snapshot()
1497 assert.True(t, deadlines[len(deadlines)-1].IsZero(), "Test %d (%s): the deadline should be cleared at EOF", i, tc.name)
1498 }
1499 }
1500
1501 // TestIdleTimeoutReaderHandlerDeadlineAfterTimeout checks that a
1502 // handler can't lift the expired deadline bounding the HTTP/1 drain of
1503 // a body that timed out.
1504 func TestIdleTimeoutReaderHandlerDeadlineAfterTimeout(t *testing.T) {
1505 w := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
1506 r := &IdleTimeoutReader{
1507 ReadCloser: io.NopCloser(iotest.ErrReader(os.ErrDeadlineExceeded)),
1508 Ctrl: http.NewResponseController(w),
1509 Deadline: IdleDeadline{Timeout: -time.Second},
1510 Logger: zap.NewNop(),
1511 DrainDeadline: true,
1512 }
1513 _, err := r.Read(make([]byte, 1))
1514 require.ErrorIs(t, err, os.ErrDeadlineExceeded)
1515 expired := w.snapshot()[0]
1516
1517 require.NoError(t, r.SetReadDeadline(time.Time{}))
1518 r.HandlerDone()
1519 deadlines := w.snapshot()
1520 assert.Equal(t, expired, deadlines[len(deadlines)-1])
1521 }
1522
1523 // TestIdleTimeoutReaderHandlerDoneClearsReleasedHardDeadline checks
1524 // that the hard deadline put back after a partial read doesn't outlive
1525 // the handler when it returns without reading the rest of the body.
1526 func TestIdleTimeoutReaderHandlerDoneClearsReleasedHardDeadline(t *testing.T) {
1527 hard := time.Now().Add(time.Hour)
1528 w := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
1529 r := &IdleTimeoutReader{
1530 ReadCloser: io.NopCloser(strings.NewReader("abcd")),
1531 Ctrl: http.NewResponseController(w),
1532 Deadline: IdleDeadline{Timeout: time.Minute, HardDeadline: hard},
1533 Logger: zap.NewNop(),
1534 ClearBetweenReads: true,
1535 }
1536
1537 _, err := r.Read(make([]byte, 2))
1538 require.NoError(t, err)
1539 deadlines := w.snapshot()
1540 require.Len(t, deadlines, 2)
1541 assert.Equal(t, hard, deadlines[1], "the idle deadline should be released to the hard one")
1542
1543 r.HandlerDone()
1544 deadlines = w.snapshot()
1545 require.Len(t, deadlines, 3)
1546 assert.True(t, deadlines[2].IsZero(), "the released hard deadline should be cleared")
1547 }
1548
1549 // TestIdleTimeoutReaderRestoresHardDeadline checks that another handler's
1550 // deadline update between reads cannot bypass the wrapper's hard ceiling.
1551 func TestIdleTimeoutReaderRestoresHardDeadline(t *testing.T) {
1552 for _, clear := range []bool{false, true} {
1553 t.Run(fmt.Sprintf("clear=%t", clear), func(t *testing.T) {
1554 hard := time.Now().Add(time.Minute)
1555 rec := &readDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
1556 ctrl := http.NewResponseController(rec)
1557 r := &IdleTimeoutReader{
1558 ReadCloser: io.NopCloser(strings.NewReader("abcd")),
1559 Ctrl: ctrl,
1560 Deadline: IdleDeadline{Timeout: time.Hour, HardDeadline: hard},
1561 Logger: zap.NewNop(),
1562 ClearBetweenReads: clear,
1563 }
1564 _, err := r.Read(make([]byte, 2))
1565 require.NoError(t, err)
1566 // A handler can use its own response controller to change the deadline.
1567 require.NoError(t, ctrl.SetReadDeadline(time.Time{}))
1568 _, err = r.Read(make([]byte, 2))
1569 require.NoError(t, err)
1570 deadlines := rec.snapshot()
1571 assert.Equal(t, hard, deadlines[len(deadlines)-1])
1572 })
1573 }
1574 }
1575
1576 // TestIdleTimeoutWriterRestoresHardDeadline checks that another handler's
1577 // deadline update cannot bypass the hard ceiling on writes or the final flush.
1578 func TestIdleTimeoutWriterRestoresHardDeadline(t *testing.T) {
1579 for _, op := range []string{"write", "read from", "flush", "handler done"} {
1580 t.Run(op, func(t *testing.T) {
1581 hard := time.Now().Add(time.Minute)
1582 rec := &writeDeadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
1583 ctrl := http.NewResponseController(rec)
1584 w := &IdleTimeoutWriter{
1585 ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: rec},
1586 Ctrl: ctrl,
1587 Deadline: IdleDeadline{Timeout: time.Hour, HardDeadline: hard},
1588 Logger: zap.NewNop(),
1589 ClearBetweenWrites: true,
1590 }
1591 _, err := w.Write([]byte("x"))
1592 require.NoError(t, err)
1593 require.NoError(t, ctrl.SetWriteDeadline(time.Time{}))
1594 switch op {
1595 case "write":
1596 _, err = w.Write([]byte("x"))
1597 case "read from":
1598 _, err = w.ReadFrom(strings.NewReader("x"))
1599 case "flush":
1600 err = w.FlushError()
1601 case "handler done":
1602 w.HandlerDone()
1603 }
1604 require.NoError(t, err)
1605 assert.Equal(t, hard, rec.deadlines[len(rec.deadlines)-1])
1606 })
1607 }
1608 }
1609
1610 func TestIdleDeadlineMinRateLargeTransfer(t *testing.T) {
1611 start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
1612 const gb = 1_000_000_000
1613 for _, tc := range []struct {
1614 name string
1615 transferred int64
1616 minRate int64
1617 want time.Duration // allowance past start, excluding Timeout
1618 }{
1619 {"small", 5 * 1024 * 1024, 1024 * 1024, 5 * time.Second},
1620 {"just below overflow", 9 * gb, gb, 9 * time.Second},
1621 {"10 GB at 1 MB/s", 10 * gb, 1_000_000, 10_000 * time.Second},
1622 {"100 GB at 1 GB/s", 100 * gb, gb, 100 * time.Second},
1623 {"1 TB at 1 B/s", 1000 * gb, 1, math.MaxInt64},
1624 {"max int64 bytes", math.MaxInt64, 1, math.MaxInt64},
1625 } {
1626 t.Run(tc.name, func(t *testing.T) {
1627 d := IdleDeadline{Start: start, Timeout: time.Minute, MinRate: tc.minRate, transferred: tc.transferred}
1628 got := d.next()
1629 if tc.want == math.MaxInt64 {
1630 if !got.After(start.Add(100 * 365 * 24 * time.Hour)) {
1631 t.Fatalf("deadline = %v, want far in the future", got)
1632 }
1633 return
1634 }
1635 if want := start.Add(time.Minute + tc.want); !got.Equal(want) {
1636 t.Fatalf("deadline = %v, want %v", got, want)
1637 }
1638 })
1639 }
1640 }