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 TestRoundTripGzipEnabled(t *testing.T) {
937 tests := []struct {
938 name string
939 explicit bool
940 }{
941 {
942 name: "transparent",
943 explicit: false,
944 },
945 {
946 name: "explicit",
947 explicit: true,
948 },
949 }
950 for _, tt := range tests {
951 synctestSubtest(t, tt.name, func(t *testing.T) {
952 tc := newTestClientConn(t)
953 tc.greet()
954
955 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
956 if tt.explicit {
957 req.Header.Set("Accept-Encoding", "gzip")
958 }
959 rt := tc.roundTrip(req)
960 st := tc.wantStream(streamTypeRequest)
961
962
963 st.wantSomeHeaders(http.Header{
964 "Accept-Encoding": []string{"gzip"},
965 })
966
967
968 var buf bytes.Buffer
969 gw := gzip.NewWriter(&buf)
970 gw.Write([]byte("hello world"))
971 gw.Close()
972 st.writeHeaders(http.Header{
973 ":status": []string{"200"},
974 "content-encoding": []string{"gzip"},
975 "content-length": []string{strconv.Itoa(buf.Len())},
976 })
977 st.writeData(buf.Bytes())
978 st.CloseWrite()
979
980 rt.wantStatus(200)
981
982 if tt.explicit {
983
984
985 rt.wantBody(buf.Bytes())
986 resp, err := rt.result()
987 if err != nil {
988 t.Fatal(err)
989 }
990 if resp.Header.Get("Content-Encoding") != "gzip" {
991 t.Errorf("Content-Encoding = %q, want gzip", resp.Header.Get("Content-Encoding"))
992 }
993 if resp.Header.Get("Content-Length") != strconv.Itoa(buf.Len()) {
994 t.Errorf("Content-Length = %q, want %d", resp.Header.Get("Content-Length"), buf.Len())
995 }
996 if resp.ContentLength != int64(buf.Len()) {
997 t.Errorf("ContentLength = %d, want %d", resp.ContentLength, buf.Len())
998 }
999 if resp.Uncompressed {
1000 t.Errorf("Uncompressed = true, want false")
1001 }
1002 } else {
1003
1004
1005
1006 rt.wantBody([]byte("hello world"))
1007 resp, err := rt.result()
1008 if err != nil {
1009 t.Fatal(err)
1010 }
1011 if resp.Header.Get("Content-Encoding") != "" {
1012 t.Errorf("Content-Encoding = %q, want empty", resp.Header.Get("Content-Encoding"))
1013 }
1014 if resp.Header.Get("Content-Length") != "" {
1015 t.Errorf("Content-Length = %q, want empty", resp.Header.Get("Content-Length"))
1016 }
1017 if resp.ContentLength != -1 {
1018 t.Errorf("ContentLength = %d, want -1", resp.ContentLength)
1019 }
1020 if !resp.Uncompressed {
1021 t.Errorf("Uncompressed = false, want true")
1022 }
1023 }
1024 })
1025 }
1026 }
1027
1028 func TestRoundTripGzipDisabled(t *testing.T) {
1029 tests := []struct {
1030 name string
1031 setup func(tc *testClientConn, req *http.Request, wantHeaders http.Header)
1032 }{
1033 {
1034 name: "explicitly disabled",
1035 setup: func(tc *testClientConn, req *http.Request, wantHeaders http.Header) {
1036 tc.tr.tr1.DisableCompression = true
1037 },
1038 },
1039 {
1040 name: "HEAD request",
1041 setup: func(tc *testClientConn, req *http.Request, wantHeaders http.Header) {
1042 req.Method = "HEAD"
1043 wantHeaders.Set(":method", "HEAD")
1044 },
1045 },
1046 {
1047 name: "contains Range header",
1048 setup: func(tc *testClientConn, req *http.Request, wantHeaders http.Header) {
1049 req.Header.Set("Range", "bytes=0-10")
1050 wantHeaders.Set("Range", "bytes=0-10")
1051 },
1052 },
1053 {
1054 name: "contains Accept-Encoding-identity header",
1055 setup: func(tc *testClientConn, req *http.Request, wantHeaders http.Header) {
1056 req.Header.Set("Accept-Encoding", "identity")
1057 wantHeaders.Set("Accept-Encoding", "identity")
1058 },
1059 },
1060 }
1061 for _, tt := range tests {
1062 synctestSubtest(t, tt.name, func(t *testing.T) {
1063 tc := newTestClientConn(t)
1064 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
1065 wantHeaders := http.Header{
1066 ":authority": []string{"example.tld"},
1067 ":method": []string{"GET"},
1068 ":path": []string{"/"},
1069 ":scheme": []string{"https"},
1070 "User-Agent": []string{"Go-http-client/3.0"},
1071 }
1072 tt.setup(tc, req, wantHeaders)
1073 tc.greet()
1074
1075 rt := tc.roundTrip(req)
1076 st := tc.wantStream(streamTypeRequest)
1077
1078
1079 st.wantHeaders(wantHeaders)
1080
1081 st.writeHeaders(http.Header{
1082 ":status": []string{"200"},
1083 })
1084 rt.wantStatus(200)
1085 })
1086 }
1087 }
1088
1089 func TestRoundTripGzipWithTrailers(t *testing.T) {
1090 synctest.Test(t, func(t *testing.T) {
1091 tc := newTestClientConn(t)
1092 tc.greet()
1093
1094 req, _ := http.NewRequest("GET", "https://example.tld/", nil)
1095 rt := tc.roundTrip(req)
1096 st := tc.wantStream(streamTypeRequest)
1097
1098
1099 st.wantSomeHeaders(http.Header{
1100 "Accept-Encoding": []string{"gzip"},
1101 })
1102
1103
1104 var buf bytes.Buffer
1105 gw := gzip.NewWriter(&buf)
1106 gw.Write([]byte("hello world"))
1107 gw.Close()
1108 st.writeHeaders(http.Header{
1109 ":status": []string{"200"},
1110 "content-encoding": []string{"gzip"},
1111 "trailer": []string{"Server-Trailer-A"},
1112 })
1113 st.writeData(buf.Bytes())
1114 st.writeHeaders(http.Header{
1115 "server-trailer-a": {"valuea"},
1116 })
1117 st.CloseWrite()
1118
1119 rt.wantStatus(200)
1120 rt.wantTrailers(http.Header{
1121 "Server-Trailer-A": nil,
1122 })
1123 rt.wantBody([]byte("hello world"))
1124 rt.wantTrailers(http.Header{
1125 "Server-Trailer-A": {"valuea"},
1126 })
1127 st.wantClosed("request is complete")
1128 })
1129 }
1130
View as plain text