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
| 1 | package caddyhttp |
| 2 | |
| 3 | import ( |
| 4 | "bufio" |
| 5 | "bytes" |
| 6 | "context" |
| 7 | "io" |
| 8 | "net" |
| 9 | "net/http" |
| 10 | "net/http/httptest" |
| 11 | "strings" |
| 12 | "testing" |
| 13 | "time" |
| 14 | ) |
| 15 | |
| 16 | type responseWriterSpy interface { |
| 17 | http.ResponseWriter |
| 18 | Written() string |
| 19 | CalledReadFrom() bool |
| 20 | } |
| 21 | |
| 22 | var ( |
| 23 | _ responseWriterSpy = (*baseRespWriter)(nil) |
| 24 | _ responseWriterSpy = (*readFromRespWriter)(nil) |
| 25 | ) |
| 26 | |
| 27 | // a barebones http.ResponseWriter mock |
| 28 | type baseRespWriter []byte |
| 29 | |
| 30 | func (brw *baseRespWriter) Write(d []byte) (int, error) { |
| 31 | *brw = append(*brw, d...) |
| 32 | return len(d), nil |
| 33 | } |
| 34 | func (brw *baseRespWriter) Header() http.Header { return nil } |
| 35 | func (brw *baseRespWriter) WriteHeader(statusCode int) {} |
| 36 | func (brw *baseRespWriter) Written() string { return string(*brw) } |
| 37 | func (brw *baseRespWriter) CalledReadFrom() bool { return false } |
| 38 | |
| 39 | // an http.ResponseWriter mock that supports ReadFrom |
| 40 | type readFromRespWriter struct { |
| 41 | baseRespWriter |
| 42 | called bool |
| 43 | } |
| 44 | |
| 45 | func (rf *readFromRespWriter) ReadFrom(r io.Reader) (int64, error) { |
| 46 | rf.called = true |
| 47 | return io.Copy(&rf.baseRespWriter, r) |
| 48 | } |
| 49 | |
| 50 | func (rf *readFromRespWriter) CalledReadFrom() bool { return rf.called } |
| 51 | |
| 52 | type hijackRespWriter struct { |
| 53 | baseRespWriter |
| 54 | header http.Header |
| 55 | status int |
| 56 | conn net.Conn |
| 57 | } |
| 58 | |
| 59 | func newHijackRespWriter() *hijackRespWriter { |
| 60 | return &hijackRespWriter{ |
| 61 | header: make(http.Header), |
| 62 | conn: stubConn{}, |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | func (hrw *hijackRespWriter) Header() http.Header { |
| 67 | return hrw.header |
| 68 | } |
| 69 | |
| 70 | func (hrw *hijackRespWriter) WriteHeader(statusCode int) { |
| 71 | hrw.status = statusCode |
| 72 | } |
| 73 | |
| 74 | func (hrw *hijackRespWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { |
| 75 | br := bufio.NewReader(hrw.conn) |
| 76 | bw := bufio.NewWriter(hrw.conn) |
| 77 | return hrw.conn, bufio.NewReadWriter(br, bw), nil |
| 78 | } |
| 79 | |
| 80 | type stubConn struct{} |
| 81 | |
| 82 | func (stubConn) Read(_ []byte) (int, error) { return 0, io.EOF } |
| 83 | func (stubConn) Write(p []byte) (int, error) { return len(p), nil } |
| 84 | func (stubConn) Close() error { return nil } |
| 85 | func (stubConn) LocalAddr() net.Addr { return stubAddr("local") } |
| 86 | func (stubConn) RemoteAddr() net.Addr { return stubAddr("remote") } |
| 87 | func (stubConn) SetDeadline(time.Time) error { return nil } |
| 88 | func (stubConn) SetReadDeadline(time.Time) error { return nil } |
| 89 | func (stubConn) SetWriteDeadline(time.Time) error { return nil } |
| 90 | |
| 91 | type stubAddr string |
| 92 | |
| 93 | func (a stubAddr) Network() string { return "tcp" } |
| 94 | func (a stubAddr) String() string { return string(a) } |
| 95 | |
| 96 | func TestResponseWriterWrapperReadFrom(t *testing.T) { |
| 97 | tests := map[string]struct { |
| 98 | responseWriter responseWriterSpy |
| 99 | wantReadFrom bool |
| 100 | }{ |
| 101 | "no ReadFrom": { |
| 102 | responseWriter: &baseRespWriter{}, |
| 103 | wantReadFrom: false, |
| 104 | }, |
| 105 | "has ReadFrom": { |
| 106 | responseWriter: &readFromRespWriter{}, |
| 107 | wantReadFrom: true, |
| 108 | }, |
| 109 | } |
| 110 | for name, tt := range tests { |
| 111 | t.Run(name, func(t *testing.T) { |
| 112 | // what we expect middlewares to do: |
| 113 | type myWrapper struct { |
| 114 | *ResponseWriterWrapper |
| 115 | } |
| 116 | |
| 117 | wrapped := myWrapper{ |
| 118 | ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: tt.responseWriter}, |
| 119 | } |
| 120 | |
| 121 | const srcData = "boo!" |
| 122 | // hides everything but Read, since strings.Reader implements WriteTo it would |
| 123 | // take precedence over our ReadFrom. |
| 124 | src := struct{ io.Reader }{strings.NewReader(srcData)} |
| 125 | |
| 126 | if _, err := io.Copy(wrapped, src); err != nil { |
| 127 | t.Errorf("%s: Copy() err = %v", name, err) |
| 128 | } |
| 129 | |
| 130 | if got := tt.responseWriter.Written(); got != srcData { |
| 131 | t.Errorf("%s: data = %q, want %q", name, got, srcData) |
| 132 | } |
| 133 | |
| 134 | if tt.responseWriter.CalledReadFrom() != tt.wantReadFrom { |
| 135 | if tt.wantReadFrom { |
| 136 | t.Errorf("%s: ReadFrom() should have been called", name) |
| 137 | } else { |
| 138 | t.Errorf("%s: ReadFrom() should not have been called", name) |
| 139 | } |
| 140 | } |
| 141 | }) |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | func TestResponseWriterWrapperUnwrap(t *testing.T) { |
| 146 | w := &ResponseWriterWrapper{&baseRespWriter{}} |
| 147 | |
| 148 | if _, ok := w.Unwrap().(*baseRespWriter); !ok { |
| 149 | t.Errorf("Unwrap() doesn't return the underlying ResponseWriter") |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | func TestResponseRecorderReadFrom(t *testing.T) { |
| 154 | tests := map[string]struct { |
| 155 | responseWriter responseWriterSpy |
| 156 | shouldBuffer bool |
| 157 | wantReadFrom bool |
| 158 | }{ |
| 159 | "buffered plain": { |
| 160 | responseWriter: &baseRespWriter{}, |
| 161 | shouldBuffer: true, |
| 162 | wantReadFrom: false, |
| 163 | }, |
| 164 | "streamed plain": { |
| 165 | responseWriter: &baseRespWriter{}, |
| 166 | shouldBuffer: false, |
| 167 | wantReadFrom: false, |
| 168 | }, |
| 169 | "buffered ReadFrom": { |
| 170 | responseWriter: &readFromRespWriter{}, |
| 171 | shouldBuffer: true, |
| 172 | wantReadFrom: false, |
| 173 | }, |
| 174 | "streamed ReadFrom": { |
| 175 | responseWriter: &readFromRespWriter{}, |
| 176 | shouldBuffer: false, |
| 177 | wantReadFrom: true, |
| 178 | }, |
| 179 | } |
| 180 | for name, tt := range tests { |
| 181 | t.Run(name, func(t *testing.T) { |
| 182 | var buf bytes.Buffer |
| 183 | |
| 184 | rr := NewResponseRecorder(tt.responseWriter, &buf, func(status int, header http.Header) bool { |
| 185 | return tt.shouldBuffer |
| 186 | }) |
| 187 | |
| 188 | const srcData = "boo!" |
| 189 | // hides everything but Read, since strings.Reader implements WriteTo it would |
| 190 | // take precedence over our ReadFrom. |
| 191 | src := struct{ io.Reader }{strings.NewReader(srcData)} |
| 192 | |
| 193 | if _, err := io.Copy(rr, src); err != nil { |
| 194 | t.Errorf("Copy() err = %v", err) |
| 195 | } |
| 196 | |
| 197 | wantStreamed := srcData |
| 198 | wantBuffered := "" |
| 199 | if tt.shouldBuffer { |
| 200 | wantStreamed = "" |
| 201 | wantBuffered = srcData |
| 202 | } |
| 203 | |
| 204 | if got := tt.responseWriter.Written(); got != wantStreamed { |
| 205 | t.Errorf("streamed data = %q, want %q", got, wantStreamed) |
| 206 | } |
| 207 | if got := buf.String(); got != wantBuffered { |
| 208 | t.Errorf("buffered data = %q, want %q", got, wantBuffered) |
| 209 | } |
| 210 | |
| 211 | if tt.responseWriter.CalledReadFrom() != tt.wantReadFrom { |
| 212 | if tt.wantReadFrom { |
| 213 | t.Errorf("ReadFrom() should have been called") |
| 214 | } else { |
| 215 | t.Errorf("ReadFrom() should not have been called") |
| 216 | } |
| 217 | } |
| 218 | }) |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | func TestResponseRecorderSwitchingProtocolsIsHijackAware(t *testing.T) { |
| 223 | w := newHijackRespWriter() |
| 224 | var buf bytes.Buffer |
| 225 | |
| 226 | rr := NewResponseRecorder(w, &buf, func(status int, header http.Header) bool { |
| 227 | return true |
| 228 | }) |
| 229 | rr.WriteHeader(http.StatusSwitchingProtocols) |
| 230 | |
| 231 | if rr.Status() != http.StatusSwitchingProtocols { |
| 232 | t.Fatalf("status = %d, want %d", rr.Status(), http.StatusSwitchingProtocols) |
| 233 | } |
| 234 | if w.status != http.StatusSwitchingProtocols { |
| 235 | t.Fatalf("underlying status = %d, want %d", w.status, http.StatusSwitchingProtocols) |
| 236 | } |
| 237 | |
| 238 | hj, ok := rr.(http.Hijacker) |
| 239 | if !ok { |
| 240 | t.Fatal("response recorder does not implement http.Hijacker") |
| 241 | } |
| 242 | conn, _, err := hj.Hijack() |
| 243 | if err != nil { |
| 244 | t.Fatalf("Hijack() error = %v", err) |
| 245 | } |
| 246 | defer conn.Close() |
| 247 | |
| 248 | if rr.Buffered() { |
| 249 | t.Fatal("hijacked response should not remain buffered") |
| 250 | } |
| 251 | if rr.DetachAfterHijack(true) { |
| 252 | t.Fatal("response recorder should report hijacked state by returning false") |
| 253 | } |
| 254 | req := requestWithResponseWriterBoundary(w) |
| 255 | if DetachResponseWriterAfterHijack(rr, req, true) { |
| 256 | t.Fatal("DetachResponseWriterAfterHijack() should report false after hijack") |
| 257 | } |
| 258 | if err := rr.WriteResponse(); err != nil { |
| 259 | t.Fatalf("WriteResponse() after hijack returned error: %v", err) |
| 260 | } |
| 261 | if rr.Size() != 0 { |
| 262 | t.Fatalf("size = %d, want 0 after hijack handshake", rr.Size()) |
| 263 | } |
| 264 | if got := w.Written(); got != "" { |
| 265 | t.Fatalf("unexpected buffered body write after hijack: %q", got) |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | func TestResponseRecorderNestedDetach(t *testing.T) { |
| 270 | for _, detached := range []bool{false, true} { |
| 271 | name := "attached" |
| 272 | if detached { |
| 273 | name = "detached" |
| 274 | } |
| 275 | t.Run(name, func(t *testing.T) { |
| 276 | inner := NewResponseRecorder(newHijackRespWriter(), nil, nil) |
| 277 | // Include an intervening middleware wrapper in the recorder chain. |
| 278 | outer := NewResponseRecorder(&ResponseWriterWrapper{ResponseWriter: inner}, nil, nil) |
| 279 | outer.WriteHeader(http.StatusSwitchingProtocols) |
| 280 | req := requestWithResponseWriterBoundary(inner) |
| 281 | if !DetachResponseWriterAfterHijack(outer, req, detached) { |
| 282 | t.Fatal("detach configuration failed") |
| 283 | } |
| 284 | conn, brw, err := http.NewResponseController(outer).Hijack() |
| 285 | if err != nil { |
| 286 | t.Fatal(err) |
| 287 | } |
| 288 | defer conn.Close() |
| 289 | const payload = "stream payload" |
| 290 | if _, err := conn.Write([]byte(payload)); err != nil { |
| 291 | t.Fatal(err) |
| 292 | } |
| 293 | if _, err := brw.WriteString(payload); err != nil { |
| 294 | t.Fatal(err) |
| 295 | } |
| 296 | if err := brw.Flush(); err != nil { |
| 297 | t.Fatal(err) |
| 298 | } |
| 299 | want := 2 * len(payload) |
| 300 | if detached { |
| 301 | want = 0 |
| 302 | } |
| 303 | for name, rr := range map[string]ResponseRecorder{"inner": inner, "outer": outer} { |
| 304 | if rr.Size() != want { |
| 305 | t.Errorf("%s recorder size = %d, want %d", name, rr.Size(), want) |
| 306 | } |
| 307 | } |
| 308 | if DetachResponseWriterAfterHijack(outer, req, true) { |
| 309 | t.Error("detach should fail after hijack") |
| 310 | } |
| 311 | }) |
| 312 | } |
| 313 | } |
| 314 | |
| 315 | func TestDetachResponseWriterAfterHijackRejectsOpaqueInnerWriter(t *testing.T) { |
| 316 | raw := newHijackRespWriter() |
| 317 | opaque := struct{ http.ResponseWriter }{raw} |
| 318 | recorder := NewResponseRecorder(opaque, nil, nil) |
| 319 | req := requestWithResponseWriterBoundary(raw) |
| 320 | |
| 321 | if DetachResponseWriterAfterHijack(recorder, req, true) { |
| 322 | t.Fatal("detach succeeded through a writer without Unwrap") |
| 323 | } |
| 324 | } |
| 325 | |
| 326 | func TestDetachResponseWriterAfterHijackAtBoundary(t *testing.T) { |
| 327 | w := newHijackRespWriter() |
| 328 | req := requestWithResponseWriterBoundary(w) |
| 329 | if !DetachResponseWriterAfterHijack(w, req, true) { |
| 330 | t.Fatal("detach failed at the server-owned writer") |
| 331 | } |
| 332 | if DetachResponseWriterAfterHijack(w, nil, true) { |
| 333 | t.Fatal("detach succeeded without a request") |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | func TestDetachResponseWriterAfterHijackRejectsCycles(t *testing.T) { |
| 338 | raw := newHijackRespWriter() |
| 339 | req := requestWithResponseWriterBoundary(raw) |
| 340 | a := &cycleWriter{} |
| 341 | b := &cycleWriter{next: a} |
| 342 | a.next = b |
| 343 | |
| 344 | for name, w := range map[string]http.ResponseWriter{ |
| 345 | "uncomparable self-reference": uncomparableWriter{}, |
| 346 | "indirect cycle": a, |
| 347 | } { |
| 348 | t.Run(name, func(t *testing.T) { |
| 349 | if DetachResponseWriterAfterHijack(w, req, true) { |
| 350 | t.Fatal("detach succeeded without reaching the server-owned writer") |
| 351 | } |
| 352 | }) |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | func requestWithResponseWriterBoundary(w http.ResponseWriter) *http.Request { |
| 357 | req := httptest.NewRequest(http.MethodGet, "/", nil) |
| 358 | ctx := context.WithValue(req.Context(), responseWriterBoundaryCtxKey, w) |
| 359 | return req.WithContext(ctx) |
| 360 | } |
| 361 | |
| 362 | // targetIface is an interface that only the innermost writer in the tests |
| 363 | // below implements; it's used to assert UnwrapResponseWriterAs walks past |
| 364 | // outer wrappers to find it. |
| 365 | type targetIface interface { |
| 366 | http.ResponseWriter |
| 367 | magic() string |
| 368 | } |
| 369 | |
| 370 | type targetWriter struct { |
| 371 | baseRespWriter |
| 372 | } |
| 373 | |
| 374 | func (*targetWriter) magic() string { return "ok" } |
| 375 | |
| 376 | // plainWrapper wraps an http.ResponseWriter and forwards only the mandatory |
| 377 | // methods. It implements Unwrap() so the helper can traverse it. |
| 378 | type plainWrapper struct{ inner http.ResponseWriter } |
| 379 | |
| 380 | func (p *plainWrapper) Header() http.Header { return p.inner.Header() } |
| 381 | func (p *plainWrapper) Write(b []byte) (int, error) { return p.inner.Write(b) } |
| 382 | func (p *plainWrapper) WriteHeader(statusCode int) { p.inner.WriteHeader(statusCode) } |
| 383 | func (p *plainWrapper) Unwrap() http.ResponseWriter { return p.inner } |
| 384 | |
| 385 | func TestUnwrapResponseWriterAs(t *testing.T) { |
| 386 | inner := &targetWriter{} |
| 387 | for _, tc := range []struct { |
| 388 | name string |
| 389 | w http.ResponseWriter |
| 390 | ok bool |
| 391 | }{ |
| 392 | {"direct", inner, true}, |
| 393 | {"single wrapper", &ResponseWriterWrapper{ResponseWriter: inner}, true}, |
| 394 | {"multiple wrappers", &plainWrapper{inner: &ResponseWriterWrapper{ResponseWriter: &plainWrapper{inner: inner}}}, true}, |
| 395 | {"not found", &ResponseWriterWrapper{ResponseWriter: &baseRespWriter{}}, false}, |
| 396 | } { |
| 397 | t.Run(tc.name, func(t *testing.T) { |
| 398 | got, ok := UnwrapResponseWriterAs[targetIface](tc.w) |
| 399 | if ok != tc.ok { |
| 400 | t.Fatalf("ok = %v, want %v", ok, tc.ok) |
| 401 | } |
| 402 | if ok && got.magic() != "ok" { |
| 403 | t.Errorf("unexpected writer returned: %v", got) |
| 404 | } |
| 405 | }) |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | type selfUnwrapWriter struct{ baseRespWriter } |
| 410 | |
| 411 | func (s *selfUnwrapWriter) Unwrap() http.ResponseWriter { return s } |
| 412 | |
| 413 | func TestUnwrapResponseWriterAs_StopsOnSelfReference(t *testing.T) { |
| 414 | // A wrapper whose Unwrap returns itself must not loop forever. |
| 415 | loop := &selfUnwrapWriter{} |
| 416 | done := make(chan struct{}) |
| 417 | go func() { |
| 418 | defer close(done) |
| 419 | _, _ = UnwrapResponseWriterAs[targetIface](loop) |
| 420 | }() |
| 421 | select { |
| 422 | case <-done: |
| 423 | case <-time.After(time.Second): |
| 424 | t.Fatal("UnwrapResponseWriterAs hung on self-referential Unwrap") |
| 425 | } |
| 426 | } |
| 427 | |
| 428 | // uncomparableWriter contains a slice so the concrete type is not comparable. |
| 429 | // The previous `next == w` guard panics on this shape. Methods use value |
| 430 | // receivers so the wrapper is stored as a non-pointer in the interface. |
| 431 | type uncomparableWriter struct { |
| 432 | hdr http.Header |
| 433 | _ []byte |
| 434 | } |
| 435 | |
| 436 | func (u uncomparableWriter) Header() http.Header { return u.hdr } |
| 437 | func (u uncomparableWriter) Write(b []byte) (int, error) { return len(b), nil } |
| 438 | func (u uncomparableWriter) WriteHeader(int) {} |
| 439 | func (u uncomparableWriter) Unwrap() http.ResponseWriter { return u } |
| 440 | |
| 441 | func TestUnwrapResponseWriterAs_UncomparableWriter(t *testing.T) { |
| 442 | w := uncomparableWriter{} |
| 443 | done := make(chan struct{}) |
| 444 | var panicked any |
| 445 | go func() { |
| 446 | defer close(done) |
| 447 | defer func() { panicked = recover() }() |
| 448 | _, _ = UnwrapResponseWriterAs[targetIface](w) |
| 449 | }() |
| 450 | select { |
| 451 | case <-done: |
| 452 | if panicked != nil { |
| 453 | t.Fatalf("panicked on uncomparable writer: %v", panicked) |
| 454 | } |
| 455 | case <-time.After(time.Second): |
| 456 | t.Fatal("UnwrapResponseWriterAs hung on uncomparable self-unwrap") |
| 457 | } |
| 458 | } |
| 459 | |
| 460 | type cycleWriter struct { |
| 461 | baseRespWriter |
| 462 | next http.ResponseWriter |
| 463 | } |
| 464 | |
| 465 | func (c *cycleWriter) Unwrap() http.ResponseWriter { return c.next } |
| 466 | |
| 467 | func TestUnwrapResponseWriterAs_StopsOnIndirectCycle(t *testing.T) { |
| 468 | a := &cycleWriter{} |
| 469 | b := &cycleWriter{next: a} |
| 470 | a.next = b |
| 471 | done := make(chan struct{}) |
| 472 | go func() { |
| 473 | defer close(done) |
| 474 | _, _ = UnwrapResponseWriterAs[targetIface](a) |
| 475 | }() |
| 476 | select { |
| 477 | case <-done: |
| 478 | case <-time.After(time.Second): |
| 479 | t.Fatal("UnwrapResponseWriterAs hung on A→B→A unwrap cycle") |
| 480 | } |
| 481 | } |
| 482 | |
| 483 | type headerSpy struct { |
| 484 | baseRespWriter |
| 485 | status int |
| 486 | } |
| 487 | |
| 488 | func (h *headerSpy) WriteHeader(code int) { h.status = code } |
| 489 | |
| 490 | func TestRecordHijackedStatus(t *testing.T) { |
| 491 | inner := &headerSpy{} |
| 492 | rec := NewResponseRecorder(inner, new(bytes.Buffer), nil) |
| 493 | wrapped := &ResponseWriterWrapper{ResponseWriter: rec} |
| 494 | |
| 495 | if rec.Status() != 0 { |
| 496 | t.Fatalf("status = %d, want 0 before hijack", rec.Status()) |
| 497 | } |
| 498 | RecordHijackedStatus(wrapped, http.StatusOK) |
| 499 | if rec.Status() != http.StatusOK { |
| 500 | t.Fatalf("status = %d, want 200", rec.Status()) |
| 501 | } |
| 502 | if inner.status != 0 { |
| 503 | t.Errorf("WriteHeader(%d) called on inner writer; hijack must not write again", inner.status) |
| 504 | } |
| 505 | } |