@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 685 lines 23.6 KB
Raw
1 package reverseproxy
2
3 import (
4 "errors"
5 "fmt"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/caddyserver/caddy/v2"
14 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
15 "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
16 "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
17 )
18
19 // testProxyStatusName identifies the proxy in Proxy-Status assertions. RFC 9209
20 // wants the deployment named rather than the software, so the tests configure
21 // one the way a deployment would.
22 const testProxyStatusName = "proxy-3.example.com"
23
24 // assertRefused checks the response of a refused incremental forwarding
25 // against what RFC 10036 section 4.1 recommends.
26 func assertRefused(t *testing.T, err error, hdr http.Header) {
27 t.Helper()
28
29 var handlerErr caddyhttp.HandlerError
30 if !errors.As(err, &handlerErr) {
31 t.Fatalf("expected a caddyhttp.HandlerError, got %T: %v", err, err)
32 }
33 if handlerErr.StatusCode != http.StatusNotImplemented {
34 t.Errorf("status = %d, want %d", handlerErr.StatusCode, http.StatusNotImplemented)
35 }
36
37 want := testProxyStatusName + ";error=" + proxyErrorIncrementalRefused
38 if got := hdr.Get("Proxy-Status"); got != want {
39 t.Errorf("Proxy-Status = %q, want %q", got, want)
40 }
41 }
42
43 // incrementalHandler proxies to backendAddr under a name it can report in a
44 // Proxy-Status field.
45 func incrementalHandler(backendAddr string) *Handler {
46 h := minimalHandler(0, &Upstream{
47 Host: new(Host),
48 Dial: backendAddr,
49 })
50 h.ProxyStatusName = testProxyStatusName
51
52 return h
53 }
54
55 // incrementalRequest builds a request marked incremental; an unknown length is
56 // what a chunked request looks like once the server has parsed it.
57 func incrementalRequest(body string, knownLength bool) *http.Request {
58 var r io.Reader = strings.NewReader(body)
59 if !knownLength {
60 r = io.NopCloser(r) // opaque to httptest, so ContentLength stays -1
61 }
62 req := httptest.NewRequest(http.MethodPost, "http://example.com/", r)
63 req.Header.Set("Incremental", "?1")
64 return prepareTestRequest(req)
65 }
66
67 // lengthRequiringTransport stands in for fastcgi, which cannot forward a body
68 // whose length it does not know.
69 type lengthRequiringTransport struct{ testTransport }
70
71 func (lengthRequiringTransport) RequiresContentLength() bool { return true }
72
73 type roundTripFunc func(*http.Request) (*http.Response, error)
74
75 func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
76 return f(r)
77 }
78
79 type lengthRequiringRoundTripFunc roundTripFunc
80
81 func (f lengthRequiringRoundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
82 return roundTripFunc(f)(r)
83 }
84
85 func (lengthRequiringRoundTripFunc) RequiresContentLength() bool { return true }
86
87 type stagedBody struct {
88 release <-chan struct{}
89 sent bool
90 }
91
92 func (b *stagedBody) Read(p []byte) (int, error) {
93 if !b.sent {
94 b.sent = true
95 p[0] = 'x'
96 return 1, nil
97 }
98 <-b.release
99 return 0, io.EOF
100 }
101
102 func (*stagedBody) Close() error { return nil }
103
104 // Without buffering or a transport framing conflict, an unknown-length
105 // incremental request is streamed unchanged.
106 func TestIncrementalProxiedWhenRequestLengthUnknown(t *testing.T) {
107 var gotBody string
108 var gotLength int64
109 var gotIncremental string
110 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
111 b, _ := io.ReadAll(r.Body)
112 gotBody = string(b)
113 gotLength = r.ContentLength
114 gotIncremental = r.Header.Get("Incremental")
115 }))
116 defer backend.Close()
117
118 h := incrementalHandler(backend.Listener.Addr().String())
119
120 rec := httptest.NewRecorder()
121 if err := h.ServeHTTP(rec, incrementalRequest("hello", false), caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
122 return nil
123 })); err != nil {
124 t.Fatalf("ServeHTTP() error = %v", err)
125 }
126
127 if gotBody != "hello" {
128 t.Errorf("upstream body = %q, want %q", gotBody, "hello")
129 }
130 if gotLength != -1 {
131 t.Errorf("upstream ContentLength = %d, want -1", gotLength)
132 }
133 if gotIncremental != "?1" {
134 t.Errorf("upstream Incremental = %q, want ?1", gotIncremental)
135 }
136 }
137
138 // A transport that needs a CONTENT_LENGTH is the exception: only buffering the
139 // body in full can supply one, which is what incremental forwarding rules out.
140 func TestIncrementalRefusedWhenTransportRequiresContentLength(t *testing.T) {
141 for _, requestBuffers := range []int64{0, 4096} {
142 t.Run(fmt.Sprintf("buffers_%d", requestBuffers), func(t *testing.T) {
143 var reached bool
144 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
145 reached = true
146 }))
147 defer backend.Close()
148
149 h := incrementalHandler(backend.Listener.Addr().String())
150 h.RequestBuffers = requestBuffers
151 h.Transport = lengthRequiringTransport{testTransport{&http.Transport{}}}
152
153 rec := httptest.NewRecorder()
154 err := h.ServeHTTP(rec, incrementalRequest("hello", false), caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
155 return nil
156 }))
157
158 assertRefused(t, err, rec.Header())
159 if reached {
160 t.Error("request was forwarded upstream despite being refused")
161 }
162 })
163 }
164 }
165
166 // An unlimited buffer holds the entire body no matter its length, which RFC
167 // 10036 section 3 forbids for a message marked incremental.
168 func TestIncrementalRefusedWhenRequestBufferUnlimited(t *testing.T) {
169 var reached bool
170 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
171 reached = true
172 }))
173 defer backend.Close()
174
175 h := incrementalHandler(backend.Listener.Addr().String())
176 h.RequestBuffers = -1
177
178 rec := httptest.NewRecorder()
179 err := h.ServeHTTP(rec, incrementalRequest("hello", true), caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
180 return nil
181 }))
182
183 assertRefused(t, err, rec.Header())
184 if reached {
185 t.Error("request was forwarded upstream despite being refused")
186 }
187 }
188
189 // An explicit buffer is operator policy. An incremental request is refused
190 // instead of allowing an external header to disable that policy.
191 func TestIncrementalRefusedWhenRequestBufferConfigured(t *testing.T) {
192 h := incrementalHandler("unused.invalid")
193 h.RequestBuffers = 4096
194 rec := httptest.NewRecorder()
195 err := h.ServeHTTP(rec, incrementalRequest("hello", true), caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
196 return nil
197 }))
198 assertRefused(t, err, rec.Header())
199 }
200
201 // A transport-supplied buffer that exists only to discover Content-Length is
202 // unnecessary when the request already has one and must not delay forwarding.
203 func TestIncrementalKnownLengthRequestBypassesTransportDefault(t *testing.T) {
204 release := make(chan struct{})
205 reached := make(chan struct{})
206 h := incrementalHandler("unused.invalid")
207 h.RequestBuffers = 4096
208 h.requestBuffersFromTransport = true
209 h.Transport = lengthRequiringRoundTripFunc(func(req *http.Request) (*http.Response, error) {
210 close(reached)
211 return &http.Response{
212 StatusCode: http.StatusOK,
213 Status: "200 OK",
214 Header: make(http.Header),
215 Body: http.NoBody,
216 ContentLength: 0,
217 Request: req,
218 }, nil
219 })
220 req := httptest.NewRequest(http.MethodPost, "http://example.com/", &stagedBody{release: release})
221 req.ContentLength = 2
222 req.Header.Set("Content-Length", "2")
223 req.Header.Set("Incremental", "?1")
224 req = prepareTestRequest(req)
225
226 done := make(chan error, 1)
227 go func() {
228 done <- h.ServeHTTP(httptest.NewRecorder(), req, caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
229 return nil
230 }))
231 }()
232
233 select {
234 case <-reached:
235 case <-time.After(2 * time.Second):
236 t.Error("incremental request remained in the transport's default buffer")
237 }
238 close(release)
239 if err := <-done; err != nil {
240 t.Fatalf("ServeHTTP() error = %v", err)
241 }
242 }
243
244 // Without buffering configured there is nothing to refuse, so the request is
245 // proxied as usual.
246 func TestIncrementalProxiedWhenRequestNotBuffered(t *testing.T) {
247 var gotBody string
248 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
249 b, _ := io.ReadAll(r.Body)
250 gotBody = string(b)
251 }))
252 defer backend.Close()
253
254 h := incrementalHandler(backend.Listener.Addr().String())
255
256 rec := httptest.NewRecorder()
257 if err := h.ServeHTTP(rec, incrementalRequest("hello", false), caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
258 return nil
259 })); err != nil {
260 t.Fatalf("ServeHTTP() error = %v", err)
261 }
262
263 if gotBody != "hello" {
264 t.Errorf("upstream body = %q, want %q", gotBody, "hello")
265 }
266 }
267
268 // A bodyless request has no content to forward incrementally, so buffering it
269 // is a no-op and there is nothing to refuse.
270 func TestIncrementalProxiedWhenRequestHasNoBody(t *testing.T) {
271 var reached bool
272 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
273 reached = true
274 }))
275 defer backend.Close()
276
277 h := incrementalHandler(backend.Listener.Addr().String())
278 h.RequestBuffers = -1
279
280 req := httptest.NewRequest(http.MethodGet, "http://example.com/", nil)
281 req.Header.Set("Incremental", "?1")
282 req = prepareTestRequest(req)
283
284 rec := httptest.NewRecorder()
285 if err := h.ServeHTTP(rec, req, caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
286 return nil
287 })); err != nil {
288 t.Fatalf("ServeHTTP() error = %v", err)
289 }
290
291 if !reached {
292 t.Error("bodyless request was not forwarded upstream")
293 }
294 }
295
296 // A configured method rewrite can remove the request content before proxying.
297 // Refusal is decided after that rewrite rather than from the original request.
298 func TestIncrementalRewriteRemovesRequestContentBeforeRefusal(t *testing.T) {
299 var gotLength int64 = -1
300 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
301 gotLength = r.ContentLength
302 }))
303 defer backend.Close()
304
305 h := incrementalHandler(backend.Listener.Addr().String())
306 h.RequestBuffers = -1
307 h.Rewrite = &rewrite.Rewrite{Method: http.MethodGet}
308
309 rec := httptest.NewRecorder()
310 if err := h.ServeHTTP(rec, incrementalRequest("hello", true), caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
311 return nil
312 })); err != nil {
313 t.Fatalf("ServeHTTP() error = %v", err)
314 }
315 if gotLength != 0 {
316 t.Errorf("upstream ContentLength = %d, want 0", gotLength)
317 }
318 }
319
320 func TestIncrementalAddedByHeaderUpAfterBufferingIsRefused(t *testing.T) {
321 h := incrementalHandler("unused.invalid")
322 h.RequestBuffers = 4096
323 h.Headers = &headers.Handler{Request: &headers.HeaderOps{
324 Set: http.Header{"Incremental": []string{"?1"}},
325 }}
326 req := incrementalRequest("hello", true)
327 req.Header.Del("Incremental")
328
329 rec := httptest.NewRecorder()
330 err := h.ServeHTTP(rec, req, caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
331 return nil
332 }))
333 assertRefused(t, err, rec.Header())
334 }
335
336 // serveIncremental runs an ordinary request against a backend whose response
337 // is marked incremental. Request and response signals are independent.
338 func serveIncremental(t *testing.T, responseBuffers int64, backendFn http.HandlerFunc) (*httptest.ResponseRecorder, error) {
339 t.Helper()
340
341 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
342 w.Header().Set("Incremental", "?1")
343 backendFn(w, r)
344 }))
345 t.Cleanup(backend.Close)
346
347 h := incrementalHandler(backend.Listener.Addr().String())
348 h.ResponseBuffers = responseBuffers
349
350 req := prepareTestRequest(httptest.NewRequest(http.MethodGet, "http://example.com/", nil))
351 rec := httptest.NewRecorder()
352 err := h.ServeHTTP(rec, req, caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
353 return nil
354 }))
355 return rec, err
356 }
357
358 // An explicit response buffer is operator policy. An incremental response is
359 // refused rather than allowing the upstream to disable that policy.
360 func TestIncrementalRefusedWhenResponseBufferConfigured(t *testing.T) {
361 rec, err := serveIncremental(t, 4096, func(w http.ResponseWriter, r *http.Request) {
362 w.Header().Set("Content-Type", "text/event-stream")
363 w.WriteHeader(http.StatusOK)
364 w.(http.Flusher).Flush() // forces chunked, so the length stays unknown
365 _, _ = w.Write([]byte("data: hi\n\n"))
366 })
367 assertRefused(t, err, rec.Header())
368 }
369
370 // An unlimited buffer holds the entire response, whatever its length.
371 func TestIncrementalRefusedWhenResponseBufferUnlimited(t *testing.T) {
372 rec, err := serveIncremental(t, -1, func(w http.ResponseWriter, r *http.Request) {
373 _, _ = w.Write([]byte("data: hi\n\n"))
374 })
375
376 assertRefused(t, err, rec.Header())
377 }
378
379 // A bodyless response has no content to forward incrementally, so there is
380 // nothing to refuse even with buffering configured.
381 func TestIncrementalProxiedWhenResponseHasNoBody(t *testing.T) {
382 rec, err := serveIncremental(t, -1, func(w http.ResponseWriter, r *http.Request) {
383 w.WriteHeader(http.StatusOK)
384 })
385 if err != nil {
386 t.Fatalf("ServeHTTP() error = %v", err)
387 }
388
389 if rec.Code != http.StatusOK {
390 t.Errorf("status = %d, want 200", rec.Code)
391 }
392 }
393
394 // The upstream response is passed through untouched when no response
395 // buffering is configured.
396 func TestIncrementalProxiedWhenResponseNotBuffered(t *testing.T) {
397 rec, err := serveIncremental(t, 0, func(w http.ResponseWriter, r *http.Request) {
398 w.Header().Set("Content-Type", "text/event-stream")
399 w.WriteHeader(http.StatusOK)
400 w.(http.Flusher).Flush()
401 _, _ = w.Write([]byte("data: hi\n\n"))
402 })
403 if err != nil {
404 t.Fatalf("ServeHTTP() error = %v", err)
405 }
406
407 if rec.Code != http.StatusOK {
408 t.Errorf("status = %d, want 200", rec.Code)
409 }
410 if got := rec.Body.String(); got != "data: hi\n\n" {
411 t.Errorf("body = %q, want %q", got, "data: hi\n\n")
412 }
413 if got := rec.Header().Get("Incremental"); got != "?1" {
414 t.Errorf("downstream Incremental = %q, want ?1", got)
415 }
416 }
417
418 // Replacing an upstream response does not forward or buffer its body. The
419 // replacement remains valid even when that unused response was incremental.
420 func TestIncrementalResponseCanBeReplaced(t *testing.T) {
421 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
422 w.Header().Set("Incremental", "?1")
423 _, _ = w.Write([]byte("upstream"))
424 }))
425 defer backend.Close()
426
427 h := incrementalHandler(backend.Listener.Addr().String())
428 h.ResponseBuffers = -1
429 h.HandleResponse = []caddyhttp.ResponseHandler{{}}
430 req := prepareTestRequest(httptest.NewRequest(http.MethodGet, "http://example.com/", nil))
431 rec := httptest.NewRecorder()
432 err := h.ServeHTTP(rec, req, caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
433 _, writeErr := w.Write([]byte("replacement"))
434 return writeErr
435 }))
436 if err != nil {
437 t.Fatalf("ServeHTTP() error = %v", err)
438 }
439 if got := rec.Body.String(); got != "replacement" {
440 t.Errorf("body = %q, want %q", got, "replacement")
441 }
442 }
443
444 func TestIncrementalAddedByHeaderDownAfterBufferingIsRefused(t *testing.T) {
445 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
446 _, _ = w.Write([]byte("hello"))
447 }))
448 defer backend.Close()
449
450 h := incrementalHandler(backend.Listener.Addr().String())
451 h.ResponseBuffers = 4096
452 h.Headers = &headers.Handler{Response: &headers.RespHeaderOps{HeaderOps: &headers.HeaderOps{
453 Set: http.Header{"Incremental": []string{"?1"}},
454 }}}
455 req := prepareTestRequest(httptest.NewRequest(http.MethodGet, "http://example.com/", nil))
456 rec := httptest.NewRecorder()
457 err := h.ServeHTTP(rec, req, caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
458 return nil
459 }))
460 assertRefused(t, err, rec.Header())
461 }
462
463 // A software name identifies no deployment, so with nothing configured to
464 // identify this proxy there is no Proxy-Status field to generate: RFC 9209
465 // leaves generating it optional.
466 func TestProxyStatusOmittedWhenUnnamed(t *testing.T) {
467 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
468 defer backend.Close()
469
470 h := incrementalHandler(backend.Listener.Addr().String())
471 h.ProxyStatusName = ""
472 h.RequestBuffers = -1
473
474 rec := httptest.NewRecorder()
475 err := h.ServeHTTP(rec, incrementalRequest("hello", true), caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
476 return nil
477 }))
478
479 var handlerErr caddyhttp.HandlerError
480 if !errors.As(err, &handlerErr) || handlerErr.StatusCode != http.StatusNotImplemented {
481 t.Fatalf("expected a 501 HandlerError, got %v", err)
482 }
483 if got, ok := rec.Header()["Proxy-Status"]; ok {
484 t.Errorf("Proxy-Status = %q, want no field", got)
485 }
486 }
487
488 // RFC 9209 section 2 asks intermediaries to preserve the members already in
489 // the field, so the whole chain that handled the response stays visible.
490 func TestProxyStatusPreservesUpstreamMembers(t *testing.T) {
491 rec, err := serveIncremental(t, -1, func(w http.ResponseWriter, r *http.Request) {
492 w.Header().Set("Proxy-Status", "ExampleCDN")
493 _, _ = w.Write([]byte("data: hi\n\n"))
494 })
495
496 var handlerErr caddyhttp.HandlerError
497 if !errors.As(err, &handlerErr) || handlerErr.StatusCode != http.StatusNotImplemented {
498 t.Fatalf("expected a 501 HandlerError, got %v", err)
499 }
500
501 want := "ExampleCDN, " + testProxyStatusName + ";error=" + proxyErrorIncrementalRefused
502 if got := rec.Header().Get("Proxy-Status"); got != want {
503 t.Errorf("Proxy-Status = %q, want %q", got, want)
504 }
505 }
506
507 func TestProxyStatusDropsInvalidMembers(t *testing.T) {
508 h := &Handler{ProxyStatusName: testProxyStatusName}
509 upstream := http.Header{
510 "Proxy-Status": []string{"?1, (inner), upstream.example"},
511 }
512
513 got, ok := h.proxyStatus(upstream, proxyErrorIncrementalRefused)
514 if !ok {
515 t.Fatal("proxyStatus() did not produce a value")
516 }
517 want := testProxyStatusName + ";error=" + proxyErrorIncrementalRefused
518 if got != want {
519 t.Errorf("Proxy-Status = %q, want %q", got, want)
520 }
521 }
522
523 // A name a token cannot hold is quoted as a string instead, which RFC 9209
524 // allows just as well.
525 func TestProxyStatusNameNeedingQuotes(t *testing.T) {
526 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
527 defer backend.Close()
528
529 h := incrementalHandler(backend.Listener.Addr().String())
530 h.ProxyStatusName = "Example CDN"
531 h.RequestBuffers = -1
532
533 rec := httptest.NewRecorder()
534 _ = h.ServeHTTP(rec, incrementalRequest("hello", true), caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
535 return nil
536 }))
537
538 want := `"Example CDN";error=` + proxyErrorIncrementalRefused
539 if got := rec.Header().Get("Proxy-Status"); got != want {
540 t.Errorf("Proxy-Status = %q, want %q", got, want)
541 }
542 }
543
544 // HTTP/3 reports an unknown length for bodyless GET and HEAD requests. The
545 // transport still needs to receive these requests rather than a 501 refusal.
546 func TestIncrementalProxiedWhenBodylessRequestHasUnknownLength(t *testing.T) {
547 var reached bool
548 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
549 reached = true
550 }))
551 defer backend.Close()
552
553 h := incrementalHandler(backend.Listener.Addr().String())
554 h.RequestBuffers = 4096
555 h.Transport = lengthRequiringTransport{testTransport{&http.Transport{}}}
556
557 req := httptest.NewRequest(http.MethodGet, "http://example.com/", io.NopCloser(strings.NewReader("")))
558 req.Header.Set("Incremental", "?1")
559 req = prepareTestRequest(req)
560 if req.ContentLength != -1 {
561 t.Fatalf("ContentLength = %d, want -1", req.ContentLength)
562 }
563
564 rec := httptest.NewRecorder()
565 if err := h.ServeHTTP(rec, req, caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
566 return nil
567 })); err != nil {
568 t.Fatalf("ServeHTTP() error = %v", err)
569 }
570
571 if !reached {
572 t.Error("bodyless request was not forwarded upstream")
573 }
574 }
575
576 func TestIncrementalAddedByHeaderUpWhenBodylessRequestHasUnknownLength(t *testing.T) {
577 h := incrementalHandler("unused.invalid")
578 h.Headers = &headers.Handler{Request: &headers.HeaderOps{
579 Set: http.Header{"Incremental": []string{"?1"}},
580 }}
581 h.Transport = lengthRequiringRoundTripFunc(func(req *http.Request) (*http.Response, error) {
582 if req.ContentLength != 0 {
583 t.Errorf("ContentLength = %d, want 0", req.ContentLength)
584 }
585 if req.Body != nil {
586 t.Errorf("Body = %T, want nil", req.Body)
587 }
588 return &http.Response{
589 StatusCode: http.StatusOK,
590 Status: "200 OK",
591 Header: make(http.Header),
592 Body: http.NoBody,
593 ContentLength: 0,
594 Request: req,
595 }, nil
596 })
597
598 req := httptest.NewRequest(http.MethodGet, "http://example.com/", io.NopCloser(strings.NewReader("")))
599 req = prepareTestRequest(req)
600 if req.ContentLength != -1 {
601 t.Fatalf("ContentLength = %d, want -1", req.ContentLength)
602 }
603
604 rec := httptest.NewRecorder()
605 if err := h.ServeHTTP(rec, req, caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
606 return nil
607 })); err != nil {
608 t.Fatalf("ServeHTTP() error = %v", err)
609 }
610 }
611
612 func TestIncrementalBodylessUnknownLengthIsNormalised(t *testing.T) {
613 h := Handler{Transport: lengthRequiringTransport{testTransport{&http.Transport{}}}}
614 req := httptest.NewRequest(http.MethodGet, "http://example.com/", io.NopCloser(strings.NewReader("")))
615 req.Header.Set("Incremental", "?1")
616 repl := caddy.NewReplacer()
617 req = caddyhttp.PrepareRequest(req, repl, nil, &caddyhttp.Server{})
618
619 got, err := h.prepareRequest(req, repl)
620 if err != nil {
621 t.Fatalf("prepareRequest() error = %v", err)
622 }
623 if got.ContentLength != 0 {
624 t.Errorf("ContentLength = %d, want 0", got.ContentLength)
625 }
626 if got.Body != nil {
627 t.Errorf("Body = %T, want nil", got.Body)
628 }
629 }
630
631 // A response to HEAD advertises the length the content would have had, while
632 // carrying no content at all, so there is nothing for a buffer to hold back.
633 func TestIncrementalProxiedWhenHeadResponseAdvertisesLength(t *testing.T) {
634 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
635 w.Header().Set("Incremental", "?1")
636 w.Header().Set("Content-Length", "10000")
637 w.WriteHeader(http.StatusOK)
638 }))
639 defer backend.Close()
640
641 h := incrementalHandler(backend.Listener.Addr().String())
642 h.ResponseBuffers = -1
643
644 req := prepareTestRequest(httptest.NewRequest(http.MethodHead, "http://example.com/", nil))
645 rec := httptest.NewRecorder()
646 if err := h.ServeHTTP(rec, req, caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
647 return nil
648 })); err != nil {
649 t.Fatalf("ServeHTTP() error = %v", err)
650 }
651
652 if rec.Code != http.StatusOK {
653 t.Errorf("status = %d, want 200", rec.Code)
654 }
655 }
656
657 func TestResponseHasContent(t *testing.T) {
658 for _, tc := range []struct {
659 name string
660 method string
661 status int
662 length int64
663 body io.ReadCloser
664 want bool
665 }{
666 {name: "unknown response", method: http.MethodGet, status: http.StatusOK, length: -1, body: io.NopCloser(strings.NewReader("x")), want: true},
667 {name: "HEAD", method: http.MethodHead, status: http.StatusOK, length: 10, body: io.NopCloser(strings.NewReader(""))},
668 {name: "informational", method: http.MethodGet, status: http.StatusEarlyHints, length: -1, body: io.NopCloser(strings.NewReader(""))},
669 {name: "no content", method: http.MethodGet, status: http.StatusNoContent, length: -1, body: io.NopCloser(strings.NewReader(""))},
670 {name: "reset content", method: http.MethodGet, status: http.StatusResetContent, length: -1, body: io.NopCloser(strings.NewReader(""))},
671 {name: "not modified", method: http.MethodGet, status: http.StatusNotModified, length: -1, body: io.NopCloser(strings.NewReader(""))},
672 {name: "successful CONNECT", method: http.MethodConnect, status: http.StatusOK, length: -1, body: io.NopCloser(strings.NewReader(""))},
673 {name: "failed CONNECT", method: http.MethodConnect, status: http.StatusBadGateway, length: -1, body: io.NopCloser(strings.NewReader("x")), want: true},
674 {name: "zero length", method: http.MethodGet, status: http.StatusOK, length: 0, body: io.NopCloser(strings.NewReader(""))},
675 {name: "NoBody", method: http.MethodGet, status: http.StatusOK, length: -1, body: http.NoBody},
676 } {
677 t.Run(tc.name, func(t *testing.T) {
678 req := httptest.NewRequest(tc.method, "http://example.com/", nil)
679 res := &http.Response{StatusCode: tc.status, ContentLength: tc.length, Body: tc.body}
680 if got := responseHasContent(req, res); got != tc.want {
681 t.Errorf("responseHasContent() = %v, want %v", got, tc.want)
682 }
683 })
684 }
685 }