1
2
3
4
5 package http3
6
7 import (
8 "bytes"
9 "compress/gzip"
10 "errors"
11 "io"
12 "net/http"
13 "net/http/httptrace"
14 "net/textproto"
15 "reflect"
16 "slices"
17 "strconv"
18 "strings"
19 "testing"
20 "testing/synctest"
21
22 "golang.org/x/net/quic"
23 )
24
25 func TestRoundTripSimple(t *testing.T) {
26 synctest.Test(t, func(t *testing.T) {
27 tc := newTestClientConn(t)
28 tc.greet()
29
30 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
31 req.Header["User-Agent"] = nil
32 rt := tc.roundTrip(req)
33 st := tc.wantStream(streamTypeRequest)
34 st.wantSomeHeaders(http.Header{
35 ":authority": []string{"example.tld"},
36 ":method": []string{"GET"},
37 ":path": []string{"/"},
38 ":scheme": []string{"https"},
39 })
40 st.writeHeaders(http.Header{
41 ":status": []string{"200"},
42 "x-some-header": []string{"value"},
43 })
44 rt.wantStatus(200)
45 rt.wantHeaders(http.Header{
46 "X-Some-Header": []string{"value"},
47 })
48 })
49 }
50
51 func TestRoundTripWithBadHeaders(t *testing.T) {
52 synctest.Test(t, func(t *testing.T) {
53 tc := newTestClientConn(t)
54 tc.greet()
55
56 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
57 req.Header["Invalid\nHeader"] = []string{"x"}
58 rt := tc.roundTrip(req)
59 rt.wantError("RoundTrip fails when request contains invalid headers")
60 })
61 }
62
63 func TestRoundTripWithUnknownFrame(t *testing.T) {
64 synctest.Test(t, func(t *testing.T) {
65 tc := newTestClientConn(t)
66 tc.greet()
67
68 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
69 rt := tc.roundTrip(req)
70 st := tc.wantStream(streamTypeRequest)
71 st.wantHeaders(nil)
72
73
74 data := "frame content"
75 st.writeVarint(0x1f + 0x21)
76 st.writeVarint(int64(len(data)))
77 st.Write([]byte(data))
78
79 st.writeHeaders(http.Header{
80 ":status": []string{"200"},
81 })
82 rt.wantStatus(200)
83 })
84 }
85
86 func TestRoundTripWithInvalidPushPromise(t *testing.T) {
87
88
89
90 synctest.Test(t, func(t *testing.T) {
91 tc := newTestClientConn(t)
92 tc.greet()
93
94 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
95 rt := tc.roundTrip(req)
96 st := tc.wantStream(streamTypeRequest)
97 st.wantHeaders(nil)
98
99
100
101
102 st.writePushPromise(0, http.Header{
103 ":path": []string{"/foo"},
104 })
105 rt.wantError("RoundTrip fails after receiving invalid PUSH_PROMISE")
106 tc.wantClosed(
107 "push ID exceeds client's MAX_PUSH_ID",
108 errH3IDError,
109 )
110 })
111 }
112
113 func TestRoundTripResponseContentLength(t *testing.T) {
114 for _, test := range []struct {
115 name string
116 respHeader http.Header
117 wantContentLength int64
118 wantError bool
119 }{{
120 name: "valid",
121 respHeader: http.Header{
122 ":status": []string{"200"},
123 "content-length": []string{"100"},
124 },
125 wantContentLength: 100,
126 }, {
127 name: "absent",
128 respHeader: http.Header{
129 ":status": []string{"200"},
130 },
131 wantContentLength: -1,
132 }, {
133 name: "unparsable",
134 respHeader: http.Header{
135 ":status": []string{"200"},
136 "content-length": []string{"1 1"},
137 },
138 wantError: true,
139 }, {
140 name: "duplicated",
141 respHeader: http.Header{
142 ":status": []string{"200"},
143 "content-length": []string{"500", "500", "500"},
144 },
145 wantContentLength: 500,
146 }, {
147 name: "inconsistent",
148 respHeader: http.Header{
149 ":status": []string{"200"},
150 "content-length": []string{"1", "2"},
151 },
152 wantError: true,
153 }, {
154
155
156 name: "204",
157 respHeader: http.Header{
158 ":status": []string{"204"},
159 "content-length": []string{"100"},
160 },
161 wantContentLength: -1,
162 }} {
163 synctestSubtest(t, test.name, func(t *testing.T) {
164 tc := newTestClientConn(t)
165 tc.greet()
166
167 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
168 rt := tc.roundTrip(req)
169 st := tc.wantStream(streamTypeRequest)
170 st.wantHeaders(nil)
171 st.writeHeaders(test.respHeader)
172 if test.wantError {
173 rt.wantError("invalid content-length in response")
174 return
175 }
176 if got, want := rt.response().ContentLength, test.wantContentLength; got != want {
177 t.Errorf("Response.ContentLength = %v, want %v", got, want)
178 }
179 })
180 }
181 }
182
183 func TestRoundTripMalformedResponses(t *testing.T) {
184 for _, test := range []struct {
185 name string
186 respHeader http.Header
187 }{{
188 name: "duplicate :status",
189 respHeader: http.Header{
190 ":status": {"200", "204"},
191 },
192 }, {
193 name: "unparsable :status",
194 respHeader: http.Header{
195 ":status": {"frogpants"},
196 },
197 }, {
198 name: "undefined pseudo-header",
199 respHeader: http.Header{
200 ":status": {"200"},
201 ":unknown": {"x"},
202 },
203 }, {
204 name: "no :status",
205 respHeader: http.Header{},
206 }, {
207 name: "header name with control character",
208 respHeader: http.Header{
209 ":status": {"200"},
210 "name\nevilinjection": {"Value"},
211 },
212 }, {
213 name: "header name with uppercase character",
214 respHeader: http.Header{
215 ":status": {"200"},
216 "nAme": {"Value"},
217 },
218 }, {
219 name: "pseudo-header name with control character",
220 respHeader: http.Header{":status\nevilinjection": {"200"}},
221 }, {
222 name: "pseudo-header name with uppercase character",
223 respHeader: http.Header{":stAtus": {"200"}},
224 }, {
225 name: "header value with control character",
226 respHeader: http.Header{
227 ":status": {"200"},
228 "name": {"Value\nEvilInjection"},
229 },
230 }, {
231 name: "pseudo-header value with control character",
232 respHeader: http.Header{":status": {"200\nEvilInjection"}},
233 }} {
234 synctestSubtest(t, test.name, func(t *testing.T) {
235 tc := newTestClientConn(t)
236 tc.greet()
237
238 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
239 rt := tc.roundTrip(req)
240 st := tc.wantStream(streamTypeRequest)
241 st.wantHeaders(nil)
242 st.writeHeadersRaw(test.respHeader)
243 rt.wantError("malformed response")
244 })
245 }
246 }
247
248 func TestRoundTripCrumbledCookiesInResponse(t *testing.T) {
249
250
251
252
253 synctest.Test(t, func(t *testing.T) {
254 tc := newTestClientConn(t)
255 tc.greet()
256
257 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
258 rt := tc.roundTrip(req)
259 st := tc.wantStream(streamTypeRequest)
260 st.wantHeaders(nil)
261 st.writeHeaders(http.Header{
262 ":status": []string{"200"},
263 "cookie": []string{"a=1", "b=2; c=3", "d=4"},
264 })
265 rt.wantStatus(200)
266 rt.wantHeaders(http.Header{
267 "Cookie": []string{"a=1; b=2; c=3; d=4"},
268 })
269 })
270 }
271
272 func TestRoundTripRequestBodySent(t *testing.T) {
273 synctest.Test(t, func(t *testing.T) {
274 tc := newTestClientConn(t)
275 tc.greet()
276
277 bodyr, bodyw := io.Pipe()
278
279 req, _ := http.NewRequest("GET", "https://example.tld/", bodyr)
280 rt := tc.roundTrip(req)
281 st := tc.wantStream(streamTypeRequest)
282 st.wantHeaders(nil)
283
284 bodyw.Write([]byte{0, 1, 2, 3, 4})
285 st.wantData([]byte{0, 1, 2, 3, 4})
286
287 bodyw.Write([]byte{5, 6, 7})
288 st.wantData([]byte{5, 6, 7})
289
290 bodyw.Close()
291 st.wantClosed("request body sent")
292
293 st.writeHeaders(http.Header{
294 ":status": []string{"200"},
295 })
296 rt.wantStatus(200)
297 rt.response().Body.Close()
298 })
299 }
300
301 func TestRoundTripRequestBodyErrors(t *testing.T) {
302 for _, test := range []struct {
303 name string
304 body io.Reader
305 contentLength int64
306 }{{
307 name: "too short",
308 contentLength: 10,
309 body: bytes.NewReader([]byte{0, 1, 2, 3, 4}),
310 }, {
311 name: "too long",
312 contentLength: 5,
313 body: bytes.NewReader([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}),
314 }, {
315 name: "read error",
316 body: io.MultiReader(
317 bytes.NewReader([]byte{0, 1, 2, 3, 4}),
318 &testReader{
319 readFunc: func([]byte) (int, error) {
320 return 0, errors.New("read error")
321 },
322 },
323 ),
324 }} {
325 synctestSubtest(t, test.name, func(t *testing.T) {
326 tc := newTestClientConn(t)
327 tc.greet()
328
329 req, _ := http.NewRequest("GET", "https://example.tld/", test.body)
330 req.ContentLength = test.contentLength
331 rt := tc.roundTrip(req)
332 st := tc.wantStream(streamTypeRequest)
333
334
335
336 synctest.Wait()
337 for {
338 _, err := st.readFrameHeader()
339 if err != nil {
340 var code quic.StreamError
341 if !errors.As(err, &code) {
342 t.Fatalf("request stream closed with error %v: want QUIC stream error", err)
343 }
344 break
345 }
346 if err := st.discardFrame(); err != nil {
347 t.Fatalf("discardFrame: %v", err)
348 }
349 }
350
351
352 rt.wantError("request fails due to body error")
353 })
354 }
355 }
356
357 func TestRoundTripRequestBodyErrorAfterHeaders(t *testing.T) {
358 synctest.Test(t, func(t *testing.T) {
359 tc := newTestClientConn(t)
360 tc.greet()
361
362 bodyr, bodyw := io.Pipe()
363 req, _ := http.NewRequest("GET", "https://example.tld/", bodyr)
364 req.ContentLength = 10
365 rt := tc.roundTrip(req)
366 st := tc.wantStream(streamTypeRequest)
367
368
369
370 st.wantHeaders(nil)
371 st.writeHeaders(http.Header{
372 ":status": []string{"200"},
373 })
374 rt.wantStatus(200)
375
376
377 bodyw.Write(make([]byte, req.ContentLength+1))
378
379
380 st.wantError(quic.StreamError(errH3InternalError))
381
382 if err := rt.response().Body.Close(); err == nil {
383 t.Fatalf("Response.Body.Close() = %v, want error", err)
384 }
385 })
386 }
387
388 func TestRoundTripRequestBodyIgnored(t *testing.T) {
389 for _, tt := range []struct {
390 name string
391 sendPartialBody bool
392 }{{
393 name: "after partial body",
394 sendPartialBody: true,
395 }, {
396 name: "before any body",
397 sendPartialBody: false,
398 }} {
399 synctestSubtest(t, tt.name, func(t *testing.T) {
400 tc := newTestClientConn(t)
401 tc.greet()
402
403 bodyr, bodyw := io.Pipe()
404 req, _ := http.NewRequest("POST", "https://example.tld/", bodyr)
405 rt := tc.roundTrip(req)
406 st := tc.wantStream(streamTypeRequest)
407 st.wantHeaders(nil)
408
409 if tt.sendPartialBody {
410 bodyw.Write([]byte("hello"))
411 st.wantData([]byte("hello"))
412 }
413
414
415
416 st.CloseRead(uint64(errH3NoError))
417 st.writeHeaders(http.Header{
418 ":status": {"200"},
419 })
420 synctest.Wait()
421
422
423
424
425
426 bodyw.Write([]byte("hello again"))
427 synctest.Wait()
428 rt.wantStatus(200)
429 if err := rt.response().Body.Close(); err != nil {
430 t.Fatalf("Response.Body.Close() = %v, want nil", err)
431 }
432 })
433 }
434 }
435
436
437
438
439
440
441
442
443 func TestRoundTripClosesRequestBodyOnError(t *testing.T) {
444 for _, tt := range []struct {
445 name string
446 sendExpect100 bool
447 }{{
448
449 name: "body writer started",
450 sendExpect100: false,
451 }, {
452
453
454 name: "body writer not started",
455 sendExpect100: true,
456 }} {
457 synctestSubtest(t, tt.name, func(t *testing.T) {
458 tc := newTestClientConn(t)
459 tc.greet()
460
461 body := newTestRequestBody()
462 req, _ := http.NewRequest("POST", "https://example.tld/", body)
463 if tt.sendExpect100 {
464 req.Header.Set("Expect", "100-continue")
465 }
466 rt := tc.roundTrip(req)
467 st := tc.wantStream(streamTypeRequest)
468 st.wantHeaders(nil)
469
470
471 st.Reset(uint64(errH3InternalError))
472 rt.wantError("server reset the request stream")
473
474 if got := body.closeCount(); got != 1 {
475 t.Errorf("Request.Body closed %v times when RoundTrip returned, want 1", got)
476 }
477 })
478 }
479 }
480
481 func TestRoundTripExpect100Continue(t *testing.T) {
482 synctest.Test(t, func(t *testing.T) {
483 var callCount1xx, callCount100, callCount100Wait int
484 trace := &httptrace.ClientTrace{
485 Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
486 callCount1xx++
487 return nil
488 },
489 Got100Continue: func() {
490 callCount100++
491 },
492 Wait100Continue: func() {
493 callCount100Wait++
494 },
495 }
496
497 tc := newTestClientConn(t)
498 tc.greet()
499 clientBody := []byte("client's body that will be sent later")
500 serverBody := []byte("server's body")
501
502
503 req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), "GET", "https://example.tld/", bytes.NewBuffer(clientBody))
504 req.Header = http.Header{"Expect": {"100-continue"}}
505 rt := tc.roundTrip(req)
506 st := tc.wantStream(streamTypeRequest)
507
508
509 st.wantHeaders(nil)
510 st.wantIdle("client has yet to send its body")
511
512
513 st.writeHeaders(http.Header{
514 ":status": []string{"100"},
515 })
516
517
518 st.wantData(clientBody)
519
520
521 st.writeHeaders(http.Header{
522 ":status": []string{"200"},
523 })
524 st.writeData(serverBody)
525 st.CloseWrite()
526
527
528 rt.wantStatus(200)
529 rt.wantBody(serverBody)
530
531 gotCount := []int{callCount1xx, callCount100, callCount100Wait}
532 if !slices.Equal(gotCount, []int{1, 1, 1}) {
533 t.Errorf("Got1xxResponse, Got100Continue, and Wait100Continue was called %v times respectively, want [1 1 1]", gotCount)
534 }
535 })
536 }
537
538
539
540 func TestRoundTripInformationalHeaders(t *testing.T) {
541 for _, tt := range []struct {
542 name string
543 sendExpect100 bool
544 infoStatuses []int
545 }{
546 {
547 name: "unexpected 100 without expect header",
548 sendExpect100: false,
549 infoStatuses: []int{100},
550 },
551 {
552 name: "duplicate 100 continue",
553 sendExpect100: true,
554 infoStatuses: []int{100, 100},
555 },
556 {
557 name: "interleaved 1xx and 100 continue",
558 sendExpect100: true,
559 infoStatuses: []int{103, 100, 102},
560 },
561 {
562 name: "1xx with no 100 continue",
563 sendExpect100: true,
564 infoStatuses: []int{103, 102},
565 },
566 } {
567 synctestSubtest(t, tt.name, func(t *testing.T) {
568 tc := newTestClientConn(t)
569 tc.greet()
570
571 body := []byte("request payload")
572 req, _ := http.NewRequest("POST", "https://example.tld/", bytes.NewReader(body))
573 if tt.sendExpect100 {
574 req.Header.Set("Expect", "100-continue")
575 }
576
577 rt := tc.roundTrip(req)
578 st := tc.wantStream(streamTypeRequest)
579 st.wantHeaders(nil)
580
581 bodySent := !tt.sendExpect100
582 if bodySent {
583 st.wantData(body)
584 st.wantClosed("body sent")
585 }
586
587 for _, status := range tt.infoStatuses {
588 st.writeHeaders(http.Header{
589 ":status": {strconv.Itoa(status)},
590 })
591 if status == 100 && !bodySent {
592 bodySent = true
593 st.wantData(body)
594 st.wantClosed("body sent after 100 continue")
595 }
596 }
597
598 st.writeHeaders(http.Header{
599 ":status": {"200"},
600 })
601 st.writeData([]byte("response payload"))
602 st.CloseWrite()
603
604 rt.wantStatus(200)
605 rt.wantBody([]byte("response payload"))
606 })
607 }
608 }
609
610 func TestRoundTripExpect100ContinueRejected(t *testing.T) {
611 synctest.Test(t, func(t *testing.T) {
612 var callCount1xx, callCount100, callCount100Wait int
613 trace := &httptrace.ClientTrace{
614 Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
615 callCount1xx++
616 return nil
617 },
618 Got100Continue: func() {
619 callCount100++
620 },
621 Wait100Continue: func() {
622 callCount100Wait++
623 },
624 }
625
626 tc := newTestClientConn(t)
627 tc.greet()
628
629
630 req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), "GET", "https://example.tld/", bytes.NewBufferString("client's body"))
631 req.Header = http.Header{"Expect": {"100-continue"}}
632 rt := tc.roundTrip(req)
633 st := tc.wantStream(streamTypeRequest)
634
635
636 st.wantHeaders(nil)
637 st.wantIdle("client has yet to send its body")
638
639
640 st.writeHeaders(http.Header{
641 ":status": []string{"200"},
642 })
643 st.wantIdle("client does not send its body without getting status 100")
644 serverBody := []byte("server's body")
645 st.writeData(serverBody)
646 st.CloseWrite()
647
648 rt.wantStatus(200)
649 rt.wantBody(serverBody)
650
651 gotCount := []int{callCount1xx, callCount100, callCount100Wait}
652 if !slices.Equal(gotCount, []int{0, 0, 1}) {
653 t.Errorf("Got1xxResponse, Got100Continue, and Wait100Continue was called %v times respectively, want [0 0 1]", gotCount)
654 }
655 })
656 }
657
658 func TestRoundTripNoBodyClosesStream(t *testing.T) {
659 synctest.Test(t, func(t *testing.T) {
660 tc := newTestClientConn(t)
661 tc.greet()
662
663 req, _ := http.NewRequest("PUT", "https://example.tld/", nil)
664 tc.roundTrip(req)
665 st := tc.wantStream(streamTypeRequest)
666
667 st.wantHeaders(nil)
668 st.wantClosed("no DATA frames to send")
669 })
670 }
671
672 func TestRoundTripReadRespWithNoBody(t *testing.T) {
673 synctest.Test(t, func(t *testing.T) {
674 tc := newTestClientConn(t)
675 tc.greet()
676
677
678
679 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
680 rt := tc.roundTrip(req)
681 st := tc.wantStream(streamTypeRequest)
682 st.wantHeaders(nil)
683 st.writeHeaders(http.Header{
684 ":status": {"200"},
685 })
686 st.CloseWrite()
687 rt.wantStatus(200)
688 st.wantClosed("request is complete")
689
690
691
692 req, _ = http.NewRequest("GET", "https://example.tld/", nil)
693 rt = tc.roundTrip(req)
694 st = tc.wantStream(streamTypeRequest)
695 st.wantHeaders(nil)
696 st.writeHeaders(http.Header{
697 ":status": {"200"},
698 "content-length": {"0"},
699 })
700 rt.wantStatus(200)
701 st.wantClosed("request is complete")
702
703
704
705 req, _ = http.NewRequest("HEAD", "https://example.tld/", nil)
706 rt = tc.roundTrip(req)
707 st = tc.wantStream(streamTypeRequest)
708 st.wantHeaders(nil)
709 st.writeHeaders(http.Header{
710 ":status": {"200"},
711 "content-length": {"1000"},
712 })
713 rt.wantStatus(200)
714 st.wantClosed("request is complete")
715 })
716 }
717
718 func TestRoundTripWriteTrailer(t *testing.T) {
719 synctest.Test(t, func(t *testing.T) {
720 tc := newTestClientConn(t)
721 tc.greet()
722
723 var req *http.Request
724 req, _ = http.NewRequest("POST", "https://example.tld/", io.MultiReader(
725 testReader{readFunc: func(_ []byte) (int, error) {
726 req.Trailer["Client-Trailer-A"] = []string{"valuea"}
727
728 req.Trailer["Undeclared-Trailer"] = []string{"undeclared"}
729 return 0, io.EOF
730 }},
731 strings.NewReader("a body"),
732 testReader{readFunc: func(_ []byte) (int, error) {
733 req.Trailer["Client-Trailer-B"] = []string{"valueb"}
734
735 req.Trailer["Undeclared-Trailer"] = []string{"undeclared"}
736 return 0, io.EOF
737 }},
738 ))
739 req.Trailer = http.Header{
740 "Client-Trailer-A": nil,
741 "Client-Trailer-B": nil,
742 }
743 tc.roundTrip(req)
744 st := tc.wantStream(streamTypeRequest)
745 st.wantHeaders(nil)
746 st.wantData([]byte("a body"))
747 st.wantHeaders(http.Header{
748 "Client-Trailer-A": {"valuea"},
749 "Client-Trailer-B": {"valueb"},
750 })
751 st.wantClosed("request is complete")
752 })
753 }
754
755 func TestRoundTripWriteTrailerNoBody(t *testing.T) {
756 synctest.Test(t, func(t *testing.T) {
757 tc := newTestClientConn(t)
758 tc.greet()
759
760 var req *http.Request
761 req, _ = http.NewRequest("POST", "https://example.tld/", io.MultiReader(
762 testReader{readFunc: func(_ []byte) (int, error) {
763 req.Trailer["Client-Trailer-A"] = []string{"valuea"}
764
765 req.Trailer["Undeclared-Trailer"] = []string{"undeclared"}
766 return 0, io.EOF
767 }},
768 testReader{readFunc: func(_ []byte) (int, error) {
769 req.Trailer["Client-Trailer-B"] = []string{"valueb"}
770
771 req.Trailer["Undeclared-Trailer"] = []string{"undeclared"}
772 return 0, io.EOF
773 }},
774 ))
775 req.Trailer = http.Header{
776 "Client-Trailer-A": nil,
777 "Client-Trailer-B": nil,
778 }
779 tc.roundTrip(req)
780 st := tc.wantStream(streamTypeRequest)
781 st.wantHeaders(nil)
782 st.wantHeaders(http.Header{
783 "Client-Trailer-A": {"valuea"},
784 "Client-Trailer-B": {"valueb"},
785 })
786 st.wantClosed("request is complete")
787 })
788 }
789
790 func TestRoundTripReadTrailer(t *testing.T) {
791 synctest.Test(t, func(t *testing.T) {
792 tc := newTestClientConn(t)
793 tc.greet()
794
795 var req *http.Request
796 req, _ = http.NewRequest("GET", "https://example.tld/", nil)
797 rt := tc.roundTrip(req)
798 st := tc.wantStream(streamTypeRequest)
799
800 st.wantHeaders(nil)
801 st.writeHeaders(http.Header{
802 ":status": {"200"},
803 "trailer": {"Server-Trailer-A, Server-Trailer-B", "server-trailer-c"},
804 })
805 body := []byte("body from server")
806 st.writeData(body)
807 st.writeHeaders(http.Header{
808 "server-trailer-a": {"valuea"},
809
810 "server-trailer-c": {"valuec"},
811 "undeclared-trailer": {"undeclared"},
812 })
813
814 rt.wantStatus(200)
815
816 rt.wantHeaders(http.Header{})
817 rt.wantTrailers(http.Header{
818 "Server-Trailer-A": nil,
819 "Server-Trailer-B": nil,
820 "Server-Trailer-C": nil,
821 })
822
823
824 rt.wantBody(body)
825 rt.wantTrailers(http.Header{
826 "Server-Trailer-A": {"valuea"},
827 "Server-Trailer-B": nil,
828 "Server-Trailer-C": {"valuec"},
829
830 "Undeclared-Trailer": {"undeclared"},
831 })
832 st.wantClosed("request is complete")
833 })
834 }
835
836 func TestRoundTripReadTrailerNoBody(t *testing.T) {
837 synctest.Test(t, func(t *testing.T) {
838 tc := newTestClientConn(t)
839 tc.greet()
840
841 var req *http.Request
842 req, _ = http.NewRequest("GET", "https://example.tld/", nil)
843 rt := tc.roundTrip(req)
844 st := tc.wantStream(streamTypeRequest)
845
846 st.wantHeaders(nil)
847 st.writeHeaders(http.Header{
848 ":status": {"200"},
849 "content-length": {"0"},
850 "trailer": {"Server-Trailer-A, Server-Trailer-B", "server-trailer-c"},
851 })
852 st.writeHeaders(http.Header{
853 "server-trailer-a": {"valuea"},
854
855 "server-trailer-c": {"valuec"},
856 "undeclared-trailer": {"undeclared"},
857 })
858
859 rt.wantStatus(200)
860
861 rt.wantHeaders(http.Header{"Content-Length": {"0"}})
862 rt.wantTrailers(http.Header{
863 "Server-Trailer-A": nil,
864 "Server-Trailer-B": nil,
865 "Server-Trailer-C": nil,
866 })
867
868
869 rt.wantBody(make([]byte, 0))
870 rt.wantTrailers(http.Header{
871 "Server-Trailer-A": {"valuea"},
872 "Server-Trailer-B": nil,
873 "Server-Trailer-C": {"valuec"},
874
875 "Undeclared-Trailer": {"undeclared"},
876 })
877 st.wantClosed("request is complete")
878 })
879 }
880
881 func TestRoundTrip103EarlyHints(t *testing.T) {
882 synctest.Test(t, func(t *testing.T) {
883 firstHeader := http.Header{
884 ":status": {"103"},
885 "Link": {"</style.css>; rel=preload; as=style"},
886 }
887 secondHeader := http.Header{
888 ":status": {"103"},
889 "Link": {"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script"},
890 }
891
892 var respCounter int
893 trace := &httptrace.ClientTrace{
894 Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
895 var wantHeader textproto.MIMEHeader
896 switch respCounter {
897 case 0:
898 wantHeader = textproto.MIMEHeader(firstHeader)
899 case 1:
900 wantHeader = textproto.MIMEHeader(secondHeader)
901 default:
902 t.Error("Unexpected 1xx response")
903 }
904 wantHeader.Del(":status")
905 if !reflect.DeepEqual(header, wantHeader) {
906 t.Errorf("got %v early hints header, want %v", header, wantHeader)
907 }
908 respCounter++
909 return nil
910 },
911 }
912 req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), "GET", "https://example.tld/", nil)
913
914 tc := newTestClientConn(t)
915 tc.greet()
916 rt := tc.roundTrip(req)
917 st := tc.wantStream(streamTypeRequest)
918
919 st.wantHeaders(nil)
920 st.writeHeaders(firstHeader)
921 st.writeHeaders(secondHeader)
922
923 st.writeHeaders(http.Header{
924 ":status": {"200"},
925 })
926 body := []byte("some body")
927 st.writeData(body)
928 st.CloseWrite()
929
930 rt.wantStatus(200)
931 rt.wantBody(body)
932 st.wantClosed("request is complete")
933 })
934 }
935
936 func TestRoundTrip101SwitchingProtocols(t *testing.T) {
937 synctest.Test(t, func(t *testing.T) {
938 trace := &httptrace.ClientTrace{
939 Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
940 t.Errorf("Got1xxResponse called with code %v", code)
941 return nil
942 },
943 }
944 req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), "GET", "https://example.tld/", nil)
945
946 tc := newTestClientConn(t)
947 tc.greet()
948 rt := tc.roundTrip(req)
949 st := tc.wantStream(streamTypeRequest)
950
951 st.wantHeaders(nil)
952 st.writeHeaders({
953 ":status": {"101"},
954 })
955
956 rt.wantError("unsupported 101 response")
957 st.wantClosed("closed after error")
958 })
959 }
960
961 func TestRoundTripGzipEnabled(t *testing.T) {
962 tests := []struct {
963 name string
964 explicit bool
965 }{
966 {
967 name: "transparent",
968 explicit: false,
969 },
970 {
971 name: "explicit",
972 explicit: true,
973 },
974 }
975 for _, tt := range tests {
976 synctestSubtest(t, tt.name, func(t *testing.T) {
977 tc := newTestClientConn(t)
978 tc.greet()
979
980 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
981 if tt.explicit {
982 req.Header.Set("Accept-Encoding", "gzip")
983 }
984 rt := tc.roundTrip(req)
985 st := tc.wantStream(streamTypeRequest)
986
987
988 st.wantSomeHeaders(http.Header{
989 "Accept-Encoding": []string{"gzip"},
990 })
991
992
993 var buf bytes.Buffer
994 gw := gzip.NewWriter(&buf)
995 gw.Write([]byte("hello world"))
996 gw.Close()
997 st.writeHeaders(http.Header{
998 ":status": []string{"200"},
999 "content-encoding": []string{"gzip"},
1000 "content-length": []string{strconv.Itoa(buf.Len())},
1001 })
1002 st.writeData(buf.Bytes())
1003 st.CloseWrite()
1004
1005 rt.wantStatus(200)
1006
1007 if tt.explicit {
1008
1009
1010 rt.wantBody(buf.Bytes())
1011 resp, err := rt.result()
1012 if err != nil {
1013 t.Fatal(err)
1014 }
1015 if resp.Header.Get("Content-Encoding") != "gzip" {
1016 t.Errorf("Content-Encoding = %q, want gzip", resp.Header.Get("Content-Encoding"))
1017 }
1018 if resp.Header.Get("Content-Length") != strconv.Itoa(buf.Len()) {
1019 t.Errorf("Content-Length = %q, want %d", resp.Header.Get("Content-Length"), buf.Len())
1020 }
1021 if resp.ContentLength != int64(buf.Len()) {
1022 t.Errorf("ContentLength = %d, want %d", resp.ContentLength, buf.Len())
1023 }
1024 if resp.Uncompressed {
1025 t.Errorf("Uncompressed = true, want false")
1026 }
1027 } else {
1028
1029
1030
1031 rt.wantBody([]byte("hello world"))
1032 resp, err := rt.result()
1033 if err != nil {
1034 t.Fatal(err)
1035 }
1036 if resp.Header.Get("Content-Encoding") != "" {
1037 t.Errorf("Content-Encoding = %q, want empty", resp.Header.Get("Content-Encoding"))
1038 }
1039 if resp.Header.Get("Content-Length") != "" {
1040 t.Errorf("Content-Length = %q, want empty", resp.Header.Get("Content-Length"))
1041 }
1042 if resp.ContentLength != -1 {
1043 t.Errorf("ContentLength = %d, want -1", resp.ContentLength)
1044 }
1045 if !resp.Uncompressed {
1046 t.Errorf("Uncompressed = false, want true")
1047 }
1048 }
1049 })
1050 }
1051 }
1052
1053 func TestRoundTripGzipDisabled(t *testing.T) {
1054 tests := []struct {
1055 name string
1056 setup func(tc *testClientConn, req *http.Request, wantHeaders http.Header)
1057 }{
1058 {
1059 name: "explicitly disabled",
1060 setup: func(tc *testClientConn, req *http.Request, wantHeaders http.Header) {
1061 tc.tr.tr1.DisableCompression = true
1062 },
1063 },
1064 {
1065 name: "HEAD request",
1066 setup: func(tc *testClientConn, req *http.Request, wantHeaders http.Header) {
1067 req.Method = "HEAD"
1068 wantHeaders.Set(":method", "HEAD")
1069 },
1070 },
1071 {
1072 name: "contains Range header",
1073 setup: func(tc *testClientConn, req *http.Request, wantHeaders http.Header) {
1074 req.Header.Set("Range", "bytes=0-10")
1075 wantHeaders.Set("Range", "bytes=0-10")
1076 },
1077 },
1078 {
1079 name: "contains Accept-Encoding-identity header",
1080 setup: func(tc *testClientConn, req *http.Request, wantHeaders http.Header) {
1081 req.Header.Set("Accept-Encoding", "identity")
1082 wantHeaders.Set("Accept-Encoding", "identity")
1083 },
1084 },
1085 }
1086 for _, tt := range tests {
1087 synctestSubtest(t, tt.name, func(t *testing.T) {
1088 tc := newTestClientConn(t)
1089 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
1090 wantHeaders := http.Header{
1091 ":authority": []string{"example.tld"},
1092 ":method": []string{"GET"},
1093 ":path": []string{"/"},
1094 ":scheme": []string{"https"},
1095 "User-Agent": []string{"Go-http-client/3.0"},
1096 }
1097 tt.setup(tc, req, wantHeaders)
1098 tc.greet()
1099
1100 rt := tc.roundTrip(req)
1101 st := tc.wantStream(streamTypeRequest)
1102
1103
1104 st.wantHeaders(wantHeaders)
1105
1106 st.writeHeaders(http.Header{
1107 ":status": []string{"200"},
1108 })
1109 rt.wantStatus(200)
1110 })
1111 }
1112 }
1113
1114 func TestRoundTripGzipWithTrailers(t *testing.T) {
1115 synctest.Test(t, func(t *testing.T) {
1116 tc := newTestClientConn(t)
1117 tc.greet()
1118
1119 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
1120 rt := tc.roundTrip(req)
1121 st := tc.wantStream(streamTypeRequest)
1122
1123
1124 st.wantSomeHeaders(http.Header{
1125 "Accept-Encoding": []string{"gzip"},
1126 })
1127
1128
1129 var buf bytes.Buffer
1130 gw := gzip.NewWriter(&buf)
1131 gw.Write([]byte("hello world"))
1132 gw.Close()
1133 st.writeHeaders(http.Header{
1134 ":status": []string{"200"},
1135 "content-encoding": []string{"gzip"},
1136 "trailer": []string{"Server-Trailer-A"},
1137 })
1138 st.writeData(buf.Bytes())
1139 st.writeHeaders(http.Header{
1140 "server-trailer-a": {"valuea"},
1141 })
1142 st.CloseWrite()
1143
1144 rt.wantStatus(200)
1145 rt.wantTrailers(http.Header{
1146 "Server-Trailer-A": nil,
1147 })
1148 rt.wantBody([]byte("hello world"))
1149 rt.wantTrailers(http.Header{
1150 "Server-Trailer-A": {"valuea"},
1151 })
1152 st.wantClosed("request is complete")
1153 })
1154 }
1155
1156 func TestRoundTripHeaderLimits(t *testing.T) {
1157 for _, test := range []struct {
1158 name string
1159 infoHeaders []http.Header
1160 usesGot1xxResponseHook bool
1161 header http.Header
1162 trailer http.Header
1163 maxHeaderBytes int64
1164 valid bool
1165 }{{
1166 name: "within limits",
1167 header: {
1168 ":status": {"200"},
1169 "x-foo": {strings.Repeat("x", 1000)},
1170 },
1171 maxHeaderBytes: 1500,
1172 valid: true,
1173 }, {
1174 name: "too many header bytes",
1175 header: {
1176 ":status": {"200"},
1177 "x-foo": {strings.Repeat("x", 1000)},
1178 "x-bar": {strings.Repeat("x", 1000)},
1179 },
1180 maxHeaderBytes: 1500,
1181 }, {
1182 name: "1xx responses over limit without hook",
1183 infoHeaders: {{
1184 ":status": {"103"},
1185 "x-foo": {strings.Repeat("x", 1000)},
1186 }, {
1187 ":status": {"103"},
1188 "x-foo": {strings.Repeat("x", 1000)},
1189 }},
1190 header: {
1191 ":status": {"200"},
1192 },
1193 maxHeaderBytes: 1500,
1194 }, {
1195 name: "1xx responses within per-response limit with hook",
1196 infoHeaders: {{
1197 ":status": {"103"},
1198 "x-foo": {strings.Repeat("x", 1000)},
1199 }, {
1200 ":status": {"103"},
1201 "x-foo": {strings.Repeat("x", 1000)},
1202 }},
1203 usesGot1xxResponseHook: true,
1204 header: {
1205 ":status": {"200"},
1206 },
1207 maxHeaderBytes: 1500,
1208 valid: true,
1209 }, {
1210 name: "1xx responses over total limit with hook",
1211 infoHeaders: {{
1212 ":status": {"103"},
1213 "x-foo": {strings.Repeat("x", 2000)},
1214 }},
1215 usesGot1xxResponseHook: true,
1216 header: {
1217 ":status": {"200"},
1218 },
1219 maxHeaderBytes: 1500,
1220 }, {
1221 name: "1xx responses declared trailer over limit without hook",
1222 infoHeaders: {{
1223 ":status": {"103"},
1224 "trailer": {strings.Repeat("x", 1000)},
1225 }, {
1226 ":status": {"103"},
1227 "trailer": {strings.Repeat("x", 1000)},
1228 }},
1229 header: {
1230 ":status": {"200"},
1231 },
1232 maxHeaderBytes: 1500,
1233 }, {
1234 name: "declared trailer bytes over limit",
1235 header: {
1236 ":status": {"200"},
1237 "trailer": {strings.Repeat("a,", 50)},
1238 },
1239 maxHeaderBytes: 1500,
1240 }, {
1241 name: "sent trailer within limits",
1242 header: {
1243 ":status": {"200"},
1244 "trailer": {"x-foo"},
1245 },
1246 trailer: {
1247 "x-foo": {strings.Repeat("x", 1000)},
1248 },
1249 maxHeaderBytes: 1500,
1250 valid: true,
1251 }, {
1252 name: "too many sent trailer bytes",
1253 header: {
1254 ":status": {"200"},
1255 "trailer": {"x-foo, x-bar"},
1256 },
1257 trailer: {
1258 "x-foo": {strings.Repeat("x", 1000)},
1259 "x-bar": {strings.Repeat("x", 1000)},
1260 },
1261 maxHeaderBytes: 1500,
1262 }} {
1263 synctestSubtest(t, test.name, func(t *testing.T) {
1264 tc := newTestClientConn(t)
1265 tc.tr.tr1.MaxResponseHeaderBytes = test.maxHeaderBytes
1266 tc.greet()
1267
1268 ctx := t.Context()
1269 if test.usesGot1xxResponseHook {
1270 ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
1271 Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
1272 return nil
1273 },
1274 })
1275 }
1276 req, _ := http.NewRequestWithContext(ctx, "GET", "https://example.tld/", nil)
1277 rt := tc.roundTrip(req)
1278 st := tc.wantStream(streamTypeRequest)
1279 st.wantHeaders(nil)
1280 for _, h := range append(test.infoHeaders, test.header) {
1281 st.writeHeaders(h)
1282 if rt.done() {
1283 break
1284 }
1285 }
1286
1287 if test.trailer == nil {
1288 st.CloseWrite()
1289 if !test.valid {
1290 rt.wantError("response headers exceed limit")
1291 return
1292 }
1293 rt.wantStatus(200)
1294 return
1295 }
1296
1297 rt.wantStatus(200)
1298 st.writeHeaders(test.trailer)
1299 st.CloseWrite()
1300 _, err := rt.readBody()
1301 if (err == nil) != test.valid {
1302 t.Fatalf("readBody() = %v, want valid=%v", err, test.valid)
1303 }
1304 })
1305 }
1306 }
1307
View as plain text