Source file src/net/http/internal/http3/roundtrip_test.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     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  		// Write an unknown frame type before the response HEADERS.
    74  		data := "frame content"
    75  		st.writeVarint(0x1f + 0x21)      // reserved frame type
    76  		st.writeVarint(int64(len(data))) // size
    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  	// "A client MUST treat receipt of a PUSH_PROMISE frame that contains
    88  	// a larger push ID than the client has advertised as a connection error of H3_ID_ERROR."
    89  	// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.5-5
    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  		// Write a PUSH_PROMISE frame.
   100  		// Since the client hasn't indicated willingness to accept pushes,
   101  		// this is a connection error.
   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  		// 204 responses aren't allowed to contain a Content-Length header.
   155  		// We just ignore it.
   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  	// "If a decompressed field section contains multiple cookie field lines,
   250  	// these MUST be concatenated into a single byte string [...]"
   251  	// using the two-byte delimiter of "; "''
   252  	// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2.1-2
   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  			// The Transport should send some number of frames before detecting an
   335  			// error in the request body and aborting the request.
   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  			// RoundTrip returns with an error.
   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  		// Server sends response headers, and RoundTrip returns.
   369  		// The request body hasn't been sent yet.
   370  		st.wantHeaders(nil)
   371  		st.writeHeaders(http.Header{
   372  			":status": []string{"200"},
   373  		})
   374  		rt.wantStatus(200)
   375  
   376  		// Write too many bytes to the request body, triggering a request error.
   377  		bodyw.Write(make([]byte, req.ContentLength+1))
   378  
   379  		//io.Copy(io.Discard, st)
   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  			// Server stops reading the request because it has enough
   415  			// information already to construct its response.
   416  			st.CloseRead(uint64(errH3NoError))
   417  			st.writeHeaders(http.Header{
   418  				":status": {"200"},
   419  			})
   420  			synctest.Wait()
   421  
   422  			// Further writes will fail due to the stream being reset after the
   423  			// server closes its read. In this case, the transport should
   424  			// gracefully stop writing and surface the response it has
   425  			// received, rather than erroring out.
   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  // TestRoundTripClosesRequestBodyOnError verifies that a RoundTrip which fails
   437  // closes the request body before returning, rather than leaving the body
   438  // writer goroutine to close it at some later point.
   439  //
   440  // net/http inspects the request body as soon as RoundTrip returns to decide
   441  // whether it needs to close the body itself, so a close which happens
   442  // concurrently with the return is too late. See golang/go#60041.
   443  func TestRoundTripClosesRequestBodyOnError(t *testing.T) {
   444  	for _, tt := range []struct {
   445  		name          string
   446  		sendExpect100 bool
   447  	}{{
   448  		// The body writer has started, and is blocked reading from the body.
   449  		name:          "body writer started",
   450  		sendExpect100: false,
   451  	}, {
   452  		// The body writer never started, because the client is still waiting
   453  		// for the server to send 100 Continue.
   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  			// The server resets the request stream, failing the request.
   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  		// Client sends an Expect: 100-continue request.
   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  		// Server reads the header.
   509  		st.wantHeaders(nil)
   510  		st.wantIdle("client has yet to send its body")
   511  
   512  		// Server responds with HTTP status 100.
   513  		st.writeHeaders(http.Header{
   514  			":status": []string{"100"},
   515  		})
   516  
   517  		// Client sends its body after receiving HTTP status 100 response.
   518  		st.wantData(clientBody)
   519  
   520  		// The server sends its response after getting the client's body.
   521  		st.writeHeaders(http.Header{
   522  			":status": []string{"200"},
   523  		})
   524  		st.writeData(serverBody)
   525  		st.CloseWrite()
   526  
   527  		// Client receives the response from server.
   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  // TestRoundTripInformationalHeaders verifies that informational 1xx statuses
   539  // are never treated as the final status of a response.
   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, // Client sends Expect: 100-continue, but server never sends 100.
   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  		// Client sends an Expect: 100-continue request.
   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  		// Server reads the header.
   636  		st.wantHeaders(nil)
   637  		st.wantIdle("client has yet to send its body")
   638  
   639  		// Server rejects it.
   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  		// Case 1: we know response body is empty because the server closes the
   678  		// write direction of the stream.
   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  		// Case 2: we know response body is empty because the server indicates
   691  		// a Content-Length of 0.
   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  		// Case 3: we know response body is empty because we sent a HEAD
   704  		// request.
   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  				// Transport should not send undeclared trailer.
   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  				// Transport should not send undeclared trailer.
   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  				// Transport should not send undeclared trailer.
   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  				// Transport should not send undeclared trailer.
   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"}, // Should be canonicalized.
   804  		})
   805  		body := []byte("body from server")
   806  		st.writeData(body)
   807  		st.writeHeaders(http.Header{
   808  			"server-trailer-a": {"valuea"},
   809  			// Note that Server-Trailer-B is skipped.
   810  			"server-trailer-c":   {"valuec"},
   811  			"undeclared-trailer": {"undeclared"},
   812  		})
   813  
   814  		rt.wantStatus(200)
   815  		// Trailer is stripped off from http.Response.Header and given in http.Response.Trailer.
   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  		// Trailer updated after reading the body to EOF.
   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  			// Transport should accept undeclared trailers.
   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"}, // Should be canonicalized.
   851  		})
   852  		st.writeHeaders(http.Header{
   853  			"server-trailer-a": {"valuea"},
   854  			// Note that Server-Trailer-B is skipped.
   855  			"server-trailer-c":   {"valuec"},
   856  			"undeclared-trailer": {"undeclared"},
   857  		})
   858  
   859  		rt.wantStatus(200)
   860  		// Trailer is stripped off from http.Response.Header and given in http.Response.Trailer.
   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  		// Trailer updated after reading the empty body to EOF.
   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  			// Transport should accept undeclared trailers.
   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  			// Verify that client sends Accept-Encoding: gzip.
   963  			st.wantSomeHeaders(http.Header{
   964  				"Accept-Encoding": []string{"gzip"},
   965  			})
   966  
   967  			// Server responds with gzip.
   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  				// When user explicitly sets gzip, the server response should
   984  				// be given as is.
   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  				// When gzip is transparently set, we automatically decode the
  1004  				// response body, and make sure stale information about the
  1005  				// gzip content length and encoding are updated.
  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  			// Verify that client does not send Accept-Encoding: gzip.
  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  		// Verify that client sends Accept-Encoding: gzip.
  1099  		st.wantSomeHeaders(http.Header{
  1100  			"Accept-Encoding": []string{"gzip"},
  1101  		})
  1102  
  1103  		// Server responds with gzip and trailer declaration.
  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