| // Copyright 2026 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package http_test |
| |
| import ( |
| "bufio" |
| "errors" |
| "internal/nettest" |
| "internal/synctest" |
| "io" |
| "net/http" |
| "net/http/httptest" |
| "strings" |
| "testing" |
| ) |
| |
| // An http1ServerTest tests an HTTP/1 server using a fake network. |
| // It must be used in a synctest bubble. |
| type http1ServerTest struct { |
| t *testing.T |
| ts *httptest.Server |
| } |
| |
| func newHTTP1ServerTest(t *testing.T, h http.HandlerFunc) *http1ServerTest { |
| if h == nil { |
| h = func(w http.ResponseWriter, req *http.Request) {} |
| } |
| st := &http1ServerTest{ |
| t: t, |
| ts: httptest.NewTestServer(t, h), |
| } |
| return st |
| } |
| |
| // client returns a Client that sends requests to the server. |
| func (st *http1ServerTest) client() *http.Client { |
| return st.ts.Client() |
| } |
| |
| // transport returns a Transport that sends requests to the server. |
| func (st *http1ServerTest) transport() *http.Transport { |
| return st.ts.Client().Transport.(*http.Transport) |
| } |
| |
| // dial returns a connection to the server. |
| func (st *http1ServerTest) dial() *http1TestConn { |
| t := st.t |
| t.Helper() |
| nc, err := st.transport().DialContext(st.t.Context(), "tcp", "example.tld") |
| if err != nil { |
| t.Fatal(err) |
| } |
| t.Cleanup(func() { |
| nc.Close() |
| }) |
| conn := nc.(*nettest.Conn) |
| conn.SetReadError(errWouldBlock) // effectively make reads non-blocking |
| return &http1TestConn{ |
| t: st.t, |
| conn: conn, |
| bufr: bufio.NewReader(conn), |
| } |
| } |
| |
| var errWouldBlock = errors.New("would block") |
| |
| type http1TestConn struct { |
| t *testing.T |
| conn *nettest.Conn |
| bufr *bufio.Reader |
| } |
| |
| // writeMessage writes a number of CRLF-terminated lines to the connection. |
| func (tc *http1TestConn) writeMessage(lines ...string) { |
| t := tc.t |
| t.Helper() |
| if _, err := tc.conn.Write([]byte(strings.Join(lines, "\r\n") + "\r\n")); err != nil { |
| t.Fatalf("conn write: %v", err) |
| } |
| } |
| |
| // readResponse reads a response from the connection (not including the response body). |
| func (tc *http1TestConn) readResponse() *http.Response { |
| t := tc.t |
| t.Helper() |
| synctest.Wait() |
| resp, err := http.ReadResponse(tc.bufr, nil) |
| if err != nil { |
| t.Fatalf("ReadResponse: %v", err) |
| } |
| return resp |
| } |
| |
| // wantIdle asserts that the connection is not closed and has no pending data to read. |
| func (tc *http1TestConn) wantIdle() { |
| t := tc.t |
| t.Helper() |
| synctest.Wait() |
| if got, err := tc.bufr.Peek(32); len(got) != 0 || !errors.Is(err, errWouldBlock) { |
| t.Fatalf("read from conn: %q, %v; expect conn to be idle", got, err) |
| } |
| } |
| |
| // wantClosed asserts that the connection is read-closed and has no pending data to read. |
| func (tc *http1TestConn) wantClosed() { |
| t := tc.t |
| t.Helper() |
| synctest.Wait() |
| if got, err := tc.bufr.Peek(32); len(got) != 0 || err != io.EOF { |
| t.Fatalf("read from conn: %q; expect conn to be closed", got) |
| } |
| } |