Source file src/net/http/http1_server_test.go

     1  // Copyright 2026 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 http_test
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"errors"
    11  	"internal/nettest"
    12  	"io"
    13  	"net/http"
    14  	"net/http/httptest"
    15  	"slices"
    16  	"strings"
    17  	"sync"
    18  	"testing"
    19  	"testing/synctest"
    20  )
    21  
    22  // An http1ServerTest tests an HTTP/1 server using a fake network.
    23  // It must be used in a synctest bubble.
    24  type http1ServerTest struct {
    25  	t  *testing.T
    26  	ts *httptest.Server
    27  }
    28  
    29  func newHTTP1ServerTest(t *testing.T, h http.HandlerFunc) *http1ServerTest {
    30  	if h == nil {
    31  		h = func(w http.ResponseWriter, req *http.Request) {}
    32  	}
    33  	st := &http1ServerTest{
    34  		t:  t,
    35  		ts: httptest.NewTestServer(t, h),
    36  	}
    37  	return st
    38  }
    39  
    40  // client returns a Client that sends requests to the server.
    41  func (st *http1ServerTest) client() *http.Client {
    42  	return st.ts.Client()
    43  }
    44  
    45  // transport returns a Transport that sends requests to the server.
    46  func (st *http1ServerTest) transport() *http.Transport {
    47  	return st.ts.Client().Transport.(*http.Transport)
    48  }
    49  
    50  // dial returns a connection to the server.
    51  func (st *http1ServerTest) dial() *http1TestConn {
    52  	t := st.t
    53  	t.Helper()
    54  	nc, err := st.transport().DialContext(st.t.Context(), "tcp", "example.tld")
    55  	if err != nil {
    56  		t.Fatal(err)
    57  	}
    58  	t.Cleanup(func() {
    59  		nc.Close()
    60  	})
    61  	conn := nc.(*nettest.Conn)
    62  	conn.SetReadError(errWouldBlock) // effectively make reads non-blocking
    63  	return &http1TestConn{
    64  		t:    st.t,
    65  		conn: conn,
    66  		bufr: bufio.NewReader(conn),
    67  	}
    68  }
    69  
    70  var errWouldBlock = errors.New("would block")
    71  
    72  type http1TestConn struct {
    73  	t    *testing.T
    74  	conn *nettest.Conn
    75  	bufr *bufio.Reader
    76  }
    77  
    78  // writeMessage writes a number of CRLF-terminated lines to the connection.
    79  func (tc *http1TestConn) writeMessage(lines ...string) {
    80  	t := tc.t
    81  	t.Helper()
    82  	if _, err := tc.conn.Write([]byte(strings.Join(lines, "\r\n") + "\r\n")); err != nil {
    83  		t.Fatalf("conn write: %v", err)
    84  	}
    85  }
    86  
    87  // readRequest reads a request from the connection (not including the request body).
    88  func (tc *http1TestConn) readRequest() *http.Request {
    89  	t := tc.t
    90  	t.Helper()
    91  	synctest.Wait()
    92  	req, err := http.ReadRequest(tc.bufr)
    93  	if err != nil {
    94  		t.Fatalf("ReadRequest: %v", err)
    95  	}
    96  	return req
    97  }
    98  
    99  // readResponse reads a response from the connection (not including the response body).
   100  func (tc *http1TestConn) readResponse() *http.Response {
   101  	t := tc.t
   102  	t.Helper()
   103  	synctest.Wait()
   104  	resp, err := http.ReadResponse(tc.bufr, nil)
   105  	if err != nil {
   106  		t.Fatalf("ReadResponse: %v", err)
   107  	}
   108  	return resp
   109  }
   110  
   111  func (tc *http1TestConn) wantResponse(wantStart string, wantHeaders http.Header) {
   112  	t := tc.t
   113  	t.Helper()
   114  	synctest.Wait()
   115  	gotStart, err := tc.bufr.ReadString('\n')
   116  	if err != nil {
   117  		t.Fatalf("read from conn: %q, %v; want start line %q", gotStart, err, wantStart)
   118  	}
   119  	if got, want := gotStart, wantStart+"\r\n"; got != want {
   120  		t.Fatalf("read start line:\n%q\nwant:\n%q", got, want)
   121  	}
   122  	gotHeaders := make(http.Header)
   123  	for {
   124  		line, err := tc.bufr.ReadString('\n')
   125  		if err != nil {
   126  			t.Fatalf("read from conn: %v (want header)", err)
   127  		}
   128  		line, ok := strings.CutSuffix(line, "\r\n")
   129  		if !ok {
   130  			t.Fatalf("header line has no CRLF suffix: %q", line)
   131  		}
   132  		if line == "" {
   133  			break
   134  		}
   135  		k, v, ok := strings.Cut(line, ": ")
   136  		if !ok {
   137  			t.Fatalf("invalid header line: %q", line)
   138  		}
   139  		gotHeaders[k] = append(gotHeaders[k], v)
   140  	}
   141  	for k, wantv := range wantHeaders {
   142  		gotv := gotHeaders[k]
   143  		if !slices.Equal(gotv, wantv) {
   144  			t.Errorf("header %v = %q, want %q", k, gotv, wantv)
   145  		}
   146  	}
   147  	if t.Failed() {
   148  		t.FailNow()
   149  	}
   150  }
   151  
   152  // wantBytes asserts that the given bytes can be read from the connection.
   153  func (tc *http1TestConn) wantBytes(want []byte) {
   154  	t := tc.t
   155  	t.Helper()
   156  	synctest.Wait()
   157  	got := make([]byte, len(want))
   158  	n, err := io.ReadFull(tc.bufr, got)
   159  	got = got[:n]
   160  	if err != nil || !bytes.Equal(want, got) {
   161  		t.Fatalf("want bytes %q, got %q and error %v", want, got, err)
   162  	}
   163  }
   164  
   165  // wantIdle asserts that the connection is not closed and has no pending data to read.
   166  func (tc *http1TestConn) wantIdle() {
   167  	t := tc.t
   168  	t.Helper()
   169  	synctest.Wait()
   170  	if got, err := tc.bufr.Peek(32); len(got) != 0 || !errors.Is(err, errWouldBlock) {
   171  		t.Fatalf("read from conn: %q, %v; expect conn to be idle", got, err)
   172  	}
   173  }
   174  
   175  // wantClosed asserts that the connection is read-closed and has no pending data to read.
   176  func (tc *http1TestConn) wantClosed() {
   177  	t := tc.t
   178  	t.Helper()
   179  	synctest.Wait()
   180  	if got, err := tc.bufr.Peek(32); len(got) != 0 || err != io.EOF {
   181  		t.Fatalf("read from conn: %q; expect conn to be closed", got)
   182  	}
   183  }
   184  
   185  type testHandler struct {
   186  	t      *testing.T
   187  	mu     sync.Mutex
   188  	calls  []*testHandlerCall
   189  	closed bool
   190  }
   191  
   192  func newTestHandler(t *testing.T) *testHandler {
   193  	h := &testHandler{t: t}
   194  	t.Cleanup(func() {
   195  		// testHandler.Close should be called before the server shuts down.
   196  		// Catch the case where we forgot to do this.
   197  		if !h.closed {
   198  			t.Errorf("testHandler.Close not called")
   199  		}
   200  	})
   201  	return h
   202  }
   203  
   204  func (h *testHandler) Close() {
   205  	h.t.Helper()
   206  	synctest.Wait()
   207  	h.mu.Lock()
   208  	defer h.mu.Unlock()
   209  	if len(h.calls) > 0 {
   210  		h.t.Errorf("test finished with %v handler calls unhandled", len(h.calls))
   211  	}
   212  	for _, call := range h.calls {
   213  		call.exit()
   214  	}
   215  	h.calls = nil
   216  	h.closed = true
   217  }
   218  
   219  func (h *testHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   220  	call := &testHandlerCall{
   221  		w:   w,
   222  		req: req,
   223  		ch:  make(chan func()),
   224  	}
   225  	h.mu.Lock()
   226  	if h.closed {
   227  		h.t.Errorf("test handler called after close")
   228  	}
   229  	h.calls = append(h.calls, call)
   230  	h.mu.Unlock()
   231  	for f := range call.ch {
   232  		f()
   233  	}
   234  }
   235  
   236  func (h *testHandler) nextCall() *testHandlerCall {
   237  	h.t.Helper()
   238  	synctest.Wait()
   239  	h.mu.Lock()
   240  	defer h.mu.Unlock()
   241  	if len(h.calls) == 0 {
   242  		h.t.Fatal("expected server handler call, got none")
   243  	}
   244  	call := h.calls[0]
   245  	h.calls = h.calls[1:]
   246  	h.t.Cleanup(call.exit)
   247  	return call
   248  }
   249  
   250  // testHandlerCall is a call to the server handler's ServeHTTP method.
   251  type testHandlerCall struct {
   252  	w         http.ResponseWriter
   253  	req       *http.Request
   254  	closeOnce sync.Once
   255  	ch        chan func()
   256  }
   257  
   258  // do executes f in the handler's goroutine.
   259  func (call *testHandlerCall) do(f func(http.ResponseWriter, *http.Request)) {
   260  	donec := make(chan struct{})
   261  	call.ch <- func() {
   262  		defer close(donec)
   263  		f(call.w, call.req)
   264  	}
   265  	<-donec
   266  }
   267  
   268  // exit causes the handler to return.
   269  func (call *testHandlerCall) exit() {
   270  	call.closeOnce.Do(func() {
   271  		close(call.ch)
   272  	})
   273  }
   274  
   275  func joinCRLF(s ...string) string {
   276  	return strings.Join(s, "\r\n")
   277  }
   278  

View as plain text