6 "github.com/bradfitz/http2"
7 "github.com/bradfitz/http2/hpack"
16 // TestH2H1PlainGET tests whether simple HTTP/2 GET request works.
17 func TestH2H1PlainGET(t *testing.T) {
18 st := newServerTester(nil, t, noopHandler)
21 res, err := st.http2(requestParam{
22 name: "TestH2H1PlainGET",
25 t.Fatalf("Error st.http2() = %v", err)
29 if res.status != want {
30 t.Errorf("status = %v; want %v", res.status, want)
34 // TestH2H1AddXff tests that server generates X-Forwarded-For header
35 // field when forwarding request to backend.
36 func TestH2H1AddXff(t *testing.T) {
37 st := newServerTester([]string{"--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
38 xff := r.Header.Get("X-Forwarded-For")
41 t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
46 _, err := st.http2(requestParam{
47 name: "TestH2H1AddXff",
50 t.Fatalf("Error st.http2() = %v", err)
54 // TestH2H1AddXff2 tests that server appends X-Forwarded-For header
55 // field to existing one when forwarding request to backend.
56 func TestH2H1AddXff2(t *testing.T) {
57 st := newServerTester([]string{"--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
58 xff := r.Header.Get("X-Forwarded-For")
59 want := "host, 127.0.0.1"
61 t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
66 _, err := st.http2(requestParam{
67 name: "TestH2H1AddXff2",
68 header: []hpack.HeaderField{
69 pair("x-forwarded-for", "host"),
73 t.Fatalf("Error st.http2() = %v", err)
77 // TestH2H1StripXff tests that --strip-incoming-x-forwarded-for
79 func TestH2H1StripXff(t *testing.T) {
80 st := newServerTester([]string{"--strip-incoming-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
81 if xff, found := r.Header["X-Forwarded-For"]; found {
82 t.Errorf("X-Forwarded-For = %v; want nothing", xff)
87 _, err := st.http2(requestParam{
88 name: "TestH2H1StripXff1",
89 header: []hpack.HeaderField{
90 pair("x-forwarded-for", "host"),
94 t.Fatalf("Error st.http2() = %v", err)
98 // TestH2H1StripAddXff tests that --strip-incoming-x-forwarded-for and
99 // --add-x-forwarded-for options.
100 func TestH2H1StripAddXff(t *testing.T) {
102 "--strip-incoming-x-forwarded-for",
103 "--add-x-forwarded-for",
105 st := newServerTester(args, t, func(w http.ResponseWriter, r *http.Request) {
106 xff := r.Header.Get("X-Forwarded-For")
109 t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
114 _, err := st.http2(requestParam{
115 name: "TestH2H1StripAddXff",
116 header: []hpack.HeaderField{
117 pair("x-forwarded-for", "host"),
121 t.Fatalf("Error st.http2() = %v", err)
125 // TestH2H1GenerateVia tests that server generates Via header field to and
126 // from backend server.
127 func TestH2H1GenerateVia(t *testing.T) {
128 st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
129 if got, want := r.Header.Get("Via"), "2 nghttpx"; got != want {
130 t.Errorf("Via: %v; want %v", got, want)
135 res, err := st.http2(requestParam{
136 name: "TestH2H1GenerateVia",
139 t.Fatalf("Error st.http2() = %v", err)
141 if got, want := res.header.Get("Via"), "1.1 nghttpx"; got != want {
142 t.Errorf("Via: %v; want %v", got, want)
146 // TestH2H1AppendVia tests that server adds value to existing Via
147 // header field to and from backend server.
148 func TestH2H1AppendVia(t *testing.T) {
149 st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
150 if got, want := r.Header.Get("Via"), "foo, 2 nghttpx"; got != want {
151 t.Errorf("Via: %v; want %v", got, want)
153 w.Header().Add("Via", "bar")
157 res, err := st.http2(requestParam{
158 name: "TestH2H1AppendVia",
159 header: []hpack.HeaderField{
164 t.Fatalf("Error st.http2() = %v", err)
166 if got, want := res.header.Get("Via"), "bar, 1.1 nghttpx"; got != want {
167 t.Errorf("Via: %v; want %v", got, want)
171 // TestH2H1NoVia tests that server does not add value to existing Via
172 // header field to and from backend server.
173 func TestH2H1NoVia(t *testing.T) {
174 st := newServerTester([]string{"--no-via"}, t, func(w http.ResponseWriter, r *http.Request) {
175 if got, want := r.Header.Get("Via"), "foo"; got != want {
176 t.Errorf("Via: %v; want %v", got, want)
178 w.Header().Add("Via", "bar")
182 res, err := st.http2(requestParam{
183 name: "TestH2H1NoVia",
184 header: []hpack.HeaderField{
189 t.Fatalf("Error st.http2() = %v", err)
191 if got, want := res.header.Get("Via"), "bar"; got != want {
192 t.Errorf("Via: %v; want %v", got, want)
196 // TestH2H1HostRewrite tests that server rewrites host header field
197 func TestH2H1HostRewrite(t *testing.T) {
198 st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
199 w.Header().Add("request-host", r.Host)
203 res, err := st.http2(requestParam{
204 name: "TestH2H1HostRewrite",
207 t.Fatalf("Error st.http2() = %v", err)
209 if got, want := res.status, 200; got != want {
210 t.Errorf("status: %v; want %v", got, want)
212 if got, want := res.header.Get("request-host"), st.backendHost; got != want {
213 t.Errorf("request-host: %v; want %v", got, want)
217 // TestH2H1NoHostRewrite tests that server does not rewrite host
219 func TestH2H1NoHostRewrite(t *testing.T) {
220 st := newServerTester([]string{"--no-host-rewrite"}, t, func(w http.ResponseWriter, r *http.Request) {
221 w.Header().Add("request-host", r.Host)
225 res, err := st.http2(requestParam{
226 name: "TestH2H1NoHostRewrite",
229 t.Fatalf("Error st.http2() = %v", err)
231 if got, want := res.status, 200; got != want {
232 t.Errorf("status: %v; want %v", got, want)
234 if got, want := res.header.Get("request-host"), st.frontendHost; got != want {
235 t.Errorf("request-host: %v; want %v", got, want)
239 // TestH2H1BadRequestCL tests that server rejects request whose
240 // content-length header field value does not match its request body
242 func TestH2H1BadRequestCL(t *testing.T) {
243 st := newServerTester(nil, t, noopHandler)
246 // we set content-length: 1024, but the actual request body is
248 res, err := st.http2(requestParam{
249 name: "TestH2H1BadRequestCL",
251 header: []hpack.HeaderField{
252 pair("content-length", "1024"),
257 t.Fatalf("Error st.http2() = %v", err)
260 want := http2.ErrCodeProtocol
261 if res.errCode != want {
262 t.Errorf("res.errCode = %v; want %v", res.errCode, want)
266 // TestH2H1BadResponseCL tests that server returns error when
267 // content-length response header field value does not match its
268 // response body size.
269 func TestH2H1BadResponseCL(t *testing.T) {
270 st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
271 // we set content-length: 1024, but only send 3 bytes.
272 w.Header().Add("Content-Length", "1024")
273 w.Write([]byte("foo"))
277 res, err := st.http2(requestParam{
278 name: "TestH2H1BadResponseCL",
281 t.Fatalf("Error st.http2() = %v", err)
284 want := http2.ErrCodeProtocol
285 if res.errCode != want {
286 t.Errorf("res.errCode = %v; want %v", res.errCode, want)
290 // TestH2H1LocationRewrite tests location header field rewriting
292 func TestH2H1LocationRewrite(t *testing.T) {
293 st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
294 // TODO we cannot get st.ts's port number here.. 8443
295 // is just a place holder. We ignore it on rewrite.
296 w.Header().Add("Location", "http://127.0.0.1:8443/p/q?a=b#fragment")
300 res, err := st.http2(requestParam{
301 name: "TestH2H1LocationRewrite",
304 t.Fatalf("Error st.http2() = %v", err)
307 want := fmt.Sprintf("http://127.0.0.1:%v/p/q?a=b#fragment", serverPort)
308 if got := res.header.Get("Location"); got != want {
309 t.Errorf("Location: %v; want %v", got, want)
313 // TestH2H1ChunkedRequestBody tests that chunked request body works.
314 func TestH2H1ChunkedRequestBody(t *testing.T) {
315 st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
317 if got := fmt.Sprint(r.TransferEncoding); got != want {
318 t.Errorf("Transfer-Encoding: %v; want %v", got, want)
320 body, err := ioutil.ReadAll(r.Body)
322 t.Fatalf("Error reading r.body: %v", err)
325 if got := string(body); got != want {
326 t.Errorf("body: %v; want %v", got, want)
331 _, err := st.http2(requestParam{
332 name: "TestH2H1ChunkedRequestBody",
337 t.Fatalf("Error st.http2() = %v", err)
341 // TestH2H1MultipleRequestCL tests that server rejects request with
342 // multiple Content-Length request header fields.
343 func TestH2H1MultipleRequestCL(t *testing.T) {
344 st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
345 t.Errorf("server should not forward bad request")
349 res, err := st.http2(requestParam{
350 name: "TestH2H1MultipleRequestCL",
351 header: []hpack.HeaderField{
352 pair("content-length", "1"),
353 pair("content-length", "1"),
357 t.Fatalf("Error st.http2() = %v", err)
359 if got, want := res.errCode, http2.ErrCodeProtocol; got != want {
360 t.Errorf("res.errCode: %v; want %v", got, want)
364 // TestH2H1InvalidRequestCL tests that server rejects request with
365 // Content-Length which cannot be parsed as a number.
366 func TestH2H1InvalidRequestCL(t *testing.T) {
367 st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
368 t.Errorf("server should not forward bad request")
372 res, err := st.http2(requestParam{
373 name: "TestH2H1InvalidRequestCL",
374 header: []hpack.HeaderField{
375 pair("content-length", ""),
379 t.Fatalf("Error st.http2() = %v", err)
381 if got, want := res.errCode, http2.ErrCodeProtocol; got != want {
382 t.Errorf("res.errCode: %v; want %v", got, want)
386 // TestH2H1ConnectFailure tests that server handles the situation that
387 // connection attempt to HTTP/1 backend failed.
388 func TestH2H1ConnectFailure(t *testing.T) {
389 st := newServerTester(nil, t, noopHandler)
392 // shutdown backend server to simulate backend connect failure
395 res, err := st.http2(requestParam{
396 name: "TestH2H1ConnectFailure",
399 t.Fatalf("Error st.http2() = %v", err)
402 if got := res.status; got != want {
403 t.Errorf("status: %v; want %v", got, want)
407 // TestH2H1AssembleCookies tests that crumbled cookies in HTTP/2
408 // request is assembled into 1 when forwarding to HTTP/1 backend link.
409 func TestH2H1AssembleCookies(t *testing.T) {
410 st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
411 if got, want := r.Header.Get("Cookie"), "alpha; bravo; charlie"; got != want {
412 t.Errorf("Cookie: %v; want %v", got, want)
417 res, err := st.http2(requestParam{
418 name: "TestH2H1AssembleCookies",
419 header: []hpack.HeaderField{
420 pair("cookie", "alpha"),
421 pair("cookie", "bravo"),
422 pair("cookie", "charlie"),
426 t.Fatalf("Error st.http2() = %v", err)
428 if got, want := res.status, 200; got != want {
429 t.Errorf("status: %v; want %v", got, want)
433 // TestH2H1TETrailers tests that server accepts TE request header
434 // field if it has trailers only.
435 func TestH2H1TETrailers(t *testing.T) {
436 st := newServerTester(nil, t, noopHandler)
439 res, err := st.http2(requestParam{
440 name: "TestH2H1TETrailers",
441 header: []hpack.HeaderField{
442 pair("te", "trailers"),
446 t.Fatalf("Error st.http2() = %v", err)
448 if got, want := res.status, 200; got != want {
449 t.Errorf("status: %v; want %v", got, want)
453 // TestH2H1TEGzip tests that server resets stream if TE request header
454 // field contains gzip.
455 func TestH2H1TEGzip(t *testing.T) {
456 st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
457 t.Error("server should not forward bad request")
461 res, err := st.http2(requestParam{
462 name: "TestH2H1TEGzip",
463 header: []hpack.HeaderField{
468 t.Fatalf("Error st.http2() = %v", err)
470 if got, want := res.errCode, http2.ErrCodeProtocol; got != want {
471 t.Errorf("res.errCode = %v; want %v", res.errCode, want)
475 // TestH2H1SNI tests server's TLS SNI extension feature. It must
476 // choose appropriate certificate depending on the indicated
477 // server_name from client.
478 func TestH2H1SNI(t *testing.T) {
479 st := newServerTesterTLSConfig([]string{"--subcert=" + testDir + "/alt-server.key:" + testDir + "/alt-server.crt"}, t, noopHandler, &tls.Config{
480 ServerName: "alt-domain",
484 tlsConn := st.conn.(*tls.Conn)
485 connState := tlsConn.ConnectionState()
486 cert := connState.PeerCertificates[0]
488 if got, want := cert.Subject.CommonName, "alt-domain"; got != want {
489 t.Errorf("CommonName: %v; want %v", got, want)
493 // TestH2H1ServerPush tests server push using Link header field from
495 func TestH2H1ServerPush(t *testing.T) {
496 st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
497 // only resources marked as rel=preload are pushed
498 if !strings.HasPrefix(r.URL.Path, "/css/") {
499 w.Header().Add("Link", "</css/main.css>; rel=preload, </foo>, </css/theme.css>; rel=preload")
504 res, err := st.http2(requestParam{
505 name: "TestH2H1ServerPush",
508 t.Fatalf("Error st.http2() = %v", err)
510 if got, want := res.status, 200; got != want {
511 t.Errorf("res.status: %v; want %v", got, want)
513 if got, want := len(res.pushResponse), 2; got != want {
514 t.Fatalf("len(res.pushResponse): %v; want %v", got, want)
516 mainCSS := res.pushResponse[0]
517 if got, want := mainCSS.status, 200; got != want {
518 t.Errorf("mainCSS.status: %v; want %v", got, want)
520 themeCSS := res.pushResponse[1]
521 if got, want := themeCSS.status, 200; got != want {
522 t.Errorf("themeCSS.status: %v; want %v", got, want)
526 // TestH2H1RequestTrailer tests request trailer part is forwarded to
528 func TestH2H1RequestTrailer(t *testing.T) {
529 st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
530 buf := make([]byte, 4096)
532 _, err := r.Body.Read(buf)
537 t.Fatalf("r.Body.Read() = %v", err)
540 if got, want := r.Trailer.Get("foo"), "bar"; got != want {
541 t.Errorf("r.Trailer.Get(foo): %v; want %v", got, want)
546 res, err := st.http2(requestParam{
547 name: "TestH2H1RequestTrailer",
549 trailer: []hpack.HeaderField{
554 t.Fatalf("Error st.http2() = %v", err)
556 if got, want := res.status, 200; got != want {
557 t.Errorf("res.status: %v; want %v", got, want)
561 // TestH2H1HeaderFieldBuffer tests that request with header fields
562 // larger than configured buffer size is rejected.
563 func TestH2H1HeaderFieldBuffer(t *testing.T) {
564 st := newServerTester([]string{"--header-field-buffer=10"}, t, func(w http.ResponseWriter, r *http.Request) {
565 t.Fatal("execution path should not be here")
569 res, err := st.http2(requestParam{
570 name: "TestH2H1HeaderFieldBuffer",
573 t.Fatalf("Error st.http2() = %v", err)
575 if got, want := res.status, 431; got != want {
576 t.Errorf("status: %v; want %v", got, want)
580 // TestH2H1HeaderFields tests that request with header fields more
581 // than configured number is rejected.
582 func TestH2H1HeaderFields(t *testing.T) {
583 st := newServerTester([]string{"--max-header-fields=1"}, t, func(w http.ResponseWriter, r *http.Request) {
584 t.Fatal("execution path should not be here")
588 res, err := st.http2(requestParam{
589 name: "TestH2H1HeaderFields",
590 // we have at least 4 pseudo-header fields sent, and
591 // that ensures that buffer limit exceeds.
594 t.Fatalf("Error st.http2() = %v", err)
596 if got, want := res.status, 431; got != want {
597 t.Errorf("status: %v; want %v", got, want)
601 // TestH2H1Upgrade tests HTTP Upgrade to HTTP/2
602 func TestH2H1Upgrade(t *testing.T) {
603 st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {})
606 res, err := st.http1(requestParam{
607 name: "TestH2H1Upgrade",
608 header: []hpack.HeaderField{
609 pair("Connection", "Upgrade, HTTP2-Settings"),
610 pair("Upgrade", "h2c"),
611 pair("HTTP2-Settings", "AAMAAABkAAQAAP__"),
616 t.Fatalf("Error st.http1() = %v", err)
619 if got, want := res.status, 101; got != want {
620 t.Errorf("res.status: %v; want %v", got, want)
623 res, err = st.http2(requestParam{
627 t.Fatalf("Error st.http2() = %v", err)
629 if got, want := res.status, 200; got != want {
630 t.Errorf("res.status: %v; want %v", got, want)
634 // TestH2H1GracefulShutdown tests graceful shutdown.
635 func TestH2H1GracefulShutdown(t *testing.T) {
636 st := newServerTester(nil, t, noopHandler)
639 fmt.Fprint(st.conn, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
640 if err := st.fr.WriteSettings(); err != nil {
641 t.Fatalf("st.fr.WriteSettings(): %v", err)
644 header := []hpack.HeaderField{
645 pair(":method", "GET"),
646 pair(":scheme", "http"),
647 pair(":authority", st.authority),
651 for _, h := range header {
652 _ = st.enc.WriteField(h)
655 if err := st.fr.WriteHeaders(http2.HeadersFrameParam{
659 BlockFragment: st.headerBlkBuf.Bytes(),
661 t.Fatalf("st.fr.WriteHeaders(): %v", err)
664 // send SIGQUIT signal to nghttpx to perform graceful shutdown
665 st.cmd.Process.Signal(syscall.SIGQUIT)
667 // after signal, finish request body
668 if err := st.fr.WriteData(1, true, nil); err != nil {
669 t.Fatalf("st.fr.WriteData(): %v", err)
675 fr, err := st.readFrame()
679 if got := numGoAway; got != want {
680 t.Fatalf("numGoAway: %v; want %v", got, want)
684 t.Fatalf("st.readFrame(): %v", err)
686 switch f := fr.(type) {
687 case *http2.GoAwayFrame:
689 want := http2.ErrCodeNo
690 if got := f.ErrCode; got != want {
691 t.Fatalf("f.ErrCode(%v): %v; want %v", numGoAway, got, want)
695 want := (uint32(1) << 31) - 1
696 if got := f.LastStreamID; got != want {
697 t.Fatalf("f.LastStreamID(%v): %v; want %v", numGoAway, got, want)
701 if got := f.LastStreamID; got != want {
702 t.Fatalf("f.LastStreamID(%v): %v; want %v", numGoAway, got, want)
705 t.Fatalf("too many GOAWAYs received")
711 // TestH2H2MultipleResponseCL tests that server returns error if
712 // multiple Content-Length response header fields are received.
713 func TestH2H2MultipleResponseCL(t *testing.T) {
714 st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
715 w.Header().Add("content-length", "1")
716 w.Header().Add("content-length", "1")
720 res, err := st.http2(requestParam{
721 name: "TestH2H2MultipleResponseCL",
724 t.Fatalf("Error st.http2() = %v", err)
726 if got, want := res.errCode, http2.ErrCodeInternal; got != want {
727 t.Errorf("res.errCode: %v; want %v", got, want)
731 // TestH2H2InvalidResponseCL tests that server returns error if
732 // Content-Length response header field value cannot be parsed as a
734 func TestH2H2InvalidResponseCL(t *testing.T) {
735 st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
736 w.Header().Add("content-length", "")
740 res, err := st.http2(requestParam{
741 name: "TestH2H2InvalidResponseCL",
744 t.Fatalf("Error st.http2() = %v", err)
746 if got, want := res.errCode, http2.ErrCodeInternal; got != want {
747 t.Errorf("res.errCode: %v; want %v", got, want)
751 // TestH2H2ConnectFailure tests that server handles the situation that
752 // connection attempt to HTTP/2 backend failed.
753 func TestH2H2ConnectFailure(t *testing.T) {
754 st := newServerTester([]string{"--http2-bridge"}, t, noopHandler)
757 // simulate backend connect attempt failure
760 res, err := st.http2(requestParam{
761 name: "TestH2H2ConnectFailure",
764 t.Fatalf("Error st.http2() = %v", err)
767 if got := res.status; got != want {
768 t.Errorf("status: %v; want %v", got, want)
772 // TestH2H2HostRewrite tests that server rewrites host header field
773 func TestH2H2HostRewrite(t *testing.T) {
774 st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
775 w.Header().Add("request-host", r.Host)
779 res, err := st.http2(requestParam{
780 name: "TestH2H2HostRewrite",
783 t.Fatalf("Error st.http2() = %v", err)
785 if got, want := res.status, 200; got != want {
786 t.Errorf("status: %v; want %v", got, want)
788 if got, want := res.header.Get("request-host"), st.backendHost; got != want {
789 t.Errorf("request-host: %v; want %v", got, want)
793 // TestH2H2NoHostRewrite tests that server does not rewrite host
795 func TestH2H2NoHostRewrite(t *testing.T) {
796 st := newServerTester([]string{"--http2-bridge", "--no-host-rewrite"}, t, func(w http.ResponseWriter, r *http.Request) {
797 w.Header().Add("request-host", r.Host)
801 res, err := st.http2(requestParam{
802 name: "TestH2H2NoHostRewrite",
805 t.Fatalf("Error st.http2() = %v", err)
807 if got, want := res.status, 200; got != want {
808 t.Errorf("status: %v; want %v", got, want)
810 if got, want := res.header.Get("request-host"), st.frontendHost; got != want {
811 t.Errorf("request-host: %v; want %v", got, want)