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

     1  // Copyright 2025 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  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"net"
    13  	"net/http"
    14  	"testing"
    15  )
    16  
    17  // TestReadData tests servers reading request bodies, and clients reading response bodies.
    18  func TestReadData(t *testing.T) {
    19  	// These tests consist of a series of steps,
    20  	// where each step is either something arriving on the stream
    21  	// or the client/server reading from the body.
    22  	type (
    23  		// HEADERS frame arrives (headers).
    24  		receiveHeaders struct {
    25  			contentLength int64 // -1 for no content-length
    26  		}
    27  		// DATA frame header arrives.
    28  		receiveDataHeader struct {
    29  			size int64
    30  		}
    31  		// DATA frame content arrives.
    32  		receiveData struct {
    33  			size int64
    34  		}
    35  		// HEADERS frame arrives (trailers).
    36  		receiveTrailers struct{}
    37  		// Some other frame arrives.
    38  		receiveFrame struct {
    39  			ftype frameType
    40  			data  []byte
    41  		}
    42  		// Stream closed, ending the body.
    43  		receiveEOF struct{}
    44  		// Server reads from Request.Body, or client reads from Response.Body.
    45  		wantBody struct {
    46  			size int64
    47  			eof  bool
    48  		}
    49  		// Check that reading the body results in non-EOF error.
    50  		wantError struct {
    51  			// If err is not nil, also check that the error received is err.
    52  			err error
    53  		}
    54  		// Close the body.
    55  		closeBody struct{}
    56  	)
    57  	for _, test := range []struct {
    58  		name       string
    59  		respHeader http.Header
    60  		steps      []any
    61  		wantError  bool
    62  	}{{
    63  		name: "no content length",
    64  		steps: []any{
    65  			receiveHeaders{contentLength: -1},
    66  			receiveDataHeader{size: 10},
    67  			receiveData{size: 10},
    68  			receiveEOF{},
    69  			wantBody{size: 10, eof: true},
    70  		},
    71  	}, {
    72  		name: "valid content length",
    73  		steps: []any{
    74  			receiveHeaders{contentLength: 10},
    75  			receiveDataHeader{size: 10},
    76  			receiveData{size: 10},
    77  			receiveEOF{},
    78  			wantBody{size: 10, eof: true},
    79  		},
    80  	}, {
    81  		name: "data frame exceeds content length",
    82  		steps: []any{
    83  			receiveHeaders{contentLength: 5},
    84  			receiveDataHeader{size: 10},
    85  			receiveData{size: 10},
    86  			wantError{},
    87  		},
    88  	}, {
    89  		name: "data frame after all content read",
    90  		steps: []any{
    91  			receiveHeaders{contentLength: 5},
    92  			receiveDataHeader{size: 5},
    93  			receiveData{size: 5},
    94  			wantBody{size: 5},
    95  			receiveDataHeader{size: 1},
    96  			receiveData{size: 1},
    97  			wantError{},
    98  		},
    99  	}, {
   100  		name: "content length too long",
   101  		steps: []any{
   102  			receiveHeaders{contentLength: 10},
   103  			receiveDataHeader{size: 5},
   104  			receiveData{size: 5},
   105  			receiveEOF{},
   106  			wantBody{size: 5},
   107  			wantError{},
   108  		},
   109  	}, {
   110  		name: "stream ended by trailers",
   111  		steps: []any{
   112  			receiveHeaders{contentLength: -1},
   113  			receiveDataHeader{size: 5},
   114  			receiveData{size: 5},
   115  			receiveTrailers{},
   116  			wantBody{size: 5, eof: true},
   117  		},
   118  	}, {
   119  		name: "trailers and content length too long",
   120  		steps: []any{
   121  			receiveHeaders{contentLength: 10},
   122  			receiveDataHeader{size: 5},
   123  			receiveData{size: 5},
   124  			wantBody{size: 5},
   125  			receiveTrailers{},
   126  			wantError{},
   127  		},
   128  	}, {
   129  		name: "unknown frame before headers",
   130  		steps: []any{
   131  			receiveFrame{
   132  				ftype: 0x1f + 0x21, // reserved frame type
   133  				data:  []byte{1, 2, 3, 4},
   134  			},
   135  			receiveHeaders{contentLength: -1},
   136  			receiveDataHeader{size: 10},
   137  			receiveData{size: 10},
   138  			wantBody{size: 10},
   139  		},
   140  	}, {
   141  		name: "unknown frame after headers",
   142  		steps: []any{
   143  			receiveHeaders{contentLength: -1},
   144  			receiveFrame{
   145  				ftype: 0x1f + 0x21, // reserved frame type
   146  				data:  []byte{1, 2, 3, 4},
   147  			},
   148  			receiveDataHeader{size: 10},
   149  			receiveData{size: 10},
   150  			wantBody{size: 10},
   151  		},
   152  	}, {
   153  		name: "invalid frame",
   154  		steps: []any{
   155  			receiveHeaders{contentLength: -1},
   156  			receiveFrame{
   157  				ftype: frameTypeSettings, // not a valid frame on this stream
   158  				data:  []byte{1, 2, 3, 4},
   159  			},
   160  			wantError{},
   161  		},
   162  	}, {
   163  		name: "data frame consumed by several reads",
   164  		steps: []any{
   165  			receiveHeaders{contentLength: -1},
   166  			receiveDataHeader{size: 16},
   167  			receiveData{size: 16},
   168  			wantBody{size: 2},
   169  			wantBody{size: 4},
   170  			wantBody{size: 8},
   171  			wantBody{size: 2},
   172  		},
   173  	}, {
   174  		name: "read multiple frames",
   175  		steps: []any{
   176  			receiveHeaders{contentLength: -1},
   177  			receiveDataHeader{size: 2},
   178  			receiveData{size: 2},
   179  			receiveDataHeader{size: 4},
   180  			receiveData{size: 4},
   181  			receiveDataHeader{size: 8},
   182  			receiveData{size: 8},
   183  			wantBody{size: 2},
   184  			wantBody{size: 4},
   185  			wantBody{size: 8},
   186  		},
   187  	}, {
   188  		name: "read after body close",
   189  		steps: []any{
   190  			receiveHeaders{contentLength: -1},
   191  			receiveDataHeader{size: 2},
   192  			receiveData{size: 2},
   193  			receiveDataHeader{size: 4},
   194  			receiveData{size: 4},
   195  			receiveDataHeader{size: 8},
   196  			receiveData{size: 8},
   197  			wantBody{size: 2},
   198  			wantBody{size: 4},
   199  			closeBody{},
   200  			wantError{err: net.ErrClosed},
   201  		},
   202  	}} {
   203  
   204  		runTest := func(t testing.TB, h http.Header, st *testQUICStream, body func() io.ReadCloser) {
   205  			var (
   206  				bytesSent     int
   207  				bytesReceived int
   208  			)
   209  			for _, step := range test.steps {
   210  				switch step := step.(type) {
   211  				case receiveHeaders:
   212  					header := h.Clone()
   213  					if step.contentLength != -1 {
   214  						header["content-length"] = []string{
   215  							fmt.Sprint(step.contentLength),
   216  						}
   217  					}
   218  					st.writeHeaders(header)
   219  				case receiveDataHeader:
   220  					t.Logf("receive DATA frame header: size=%v", step.size)
   221  					st.writeVarint(int64(frameTypeData))
   222  					st.writeVarint(step.size)
   223  					st.Flush()
   224  				case receiveData:
   225  					t.Logf("receive DATA frame content: size=%v", step.size)
   226  					for range step.size {
   227  						st.WriteByte(byte(bytesSent))
   228  						bytesSent++
   229  					}
   230  					st.Flush()
   231  				case receiveTrailers:
   232  					st.writeHeaders(http.Header{
   233  						"x-trailer": []string{"trailer"},
   234  					})
   235  				case receiveFrame:
   236  					st.writeVarint(int64(step.ftype))
   237  					st.writeVarint(int64(len(step.data)))
   238  					st.Write(step.data)
   239  					st.Flush()
   240  				case receiveEOF:
   241  					t.Logf("receive EOF on request stream")
   242  					st.CloseWrite()
   243  				case wantBody:
   244  					t.Logf("read %v bytes from response body", step.size)
   245  					want := make([]byte, step.size)
   246  					for i := range want {
   247  						want[i] = byte(bytesReceived)
   248  						bytesReceived++
   249  					}
   250  					got := make([]byte, step.size)
   251  					n, err := body().Read(got)
   252  					got = got[:n]
   253  					if !bytes.Equal(got, want) {
   254  						t.Errorf("resp.Body.Read:")
   255  						t.Errorf("  got:  {%x}", got)
   256  						t.Fatalf("  want: {%x}", want)
   257  					}
   258  					if err != nil {
   259  						if step.eof && err == io.EOF {
   260  							continue
   261  						}
   262  						t.Fatalf("resp.Body.Read: unexpected error %v", err)
   263  					}
   264  					if step.eof {
   265  						if n, err := body().Read([]byte{0}); n != 0 || err != io.EOF {
   266  							t.Fatalf("resp.Body.Read() = %v, %v; want io.EOF", n, err)
   267  						}
   268  					}
   269  				case wantError:
   270  					n, err := body().Read([]byte{0})
   271  					if n != 0 || err == nil || err == io.EOF {
   272  						t.Fatalf("resp.Body.Read() = %v, %v; want error", n, err)
   273  					}
   274  					if step.err != nil && !errors.Is(step.err, err) {
   275  						t.Fatalf("resp.Body.Read() = %v, %v; want %v error", n, err, step.err)
   276  					}
   277  				case closeBody:
   278  					if err := body().Close(); err != nil {
   279  						t.Fatalf("resp.Body.Close() = %v, want nil", err)
   280  					}
   281  				default:
   282  					t.Fatalf("unknown test step %T", step)
   283  				}
   284  			}
   285  
   286  		}
   287  
   288  		synctestSubtest(t, test.name+"/client", func(t *testing.T) {
   289  			tc := newTestClientConn(t)
   290  			tc.greet()
   291  
   292  			req, _ := http.NewRequest("GET", "https://example.tld/", nil)
   293  			rt := tc.roundTrip(req)
   294  			st := tc.wantStream(streamTypeRequest)
   295  			st.wantHeaders(nil)
   296  
   297  			header := http.Header{
   298  				":status": []string{"200"},
   299  			}
   300  			runTest(t, header, st, func() io.ReadCloser {
   301  				return rt.response().Body
   302  			})
   303  		})
   304  	}
   305  }
   306  

View as plain text