Source file src/net/http/internal/http2/transport_internal_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 http2
     6  
     7  import (
     8  	"bytes"
     9  	"compress/gzip"
    10  	"fmt"
    11  	"io"
    12  	"io/fs"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  )
    17  
    18  type panicReader struct{}
    19  
    20  func (panicReader) Read([]byte) (int, error) { panic("unexpected Read") }
    21  func (panicReader) Close() error             { panic("unexpected Close") }
    22  
    23  func TestActualContentLength(t *testing.T) {
    24  	tests := []struct {
    25  		req  *ClientRequest
    26  		want int64
    27  	}{
    28  		// Verify we don't read from Body:
    29  		0: {
    30  			req:  &ClientRequest{Body: panicReader{}},
    31  			want: -1,
    32  		},
    33  		// nil Body means 0, regardless of ContentLength:
    34  		1: {
    35  			req:  &ClientRequest{Body: nil, ContentLength: 5},
    36  			want: 0,
    37  		},
    38  		// ContentLength is used if set.
    39  		2: {
    40  			req:  &ClientRequest{Body: panicReader{}, ContentLength: 5},
    41  			want: 5,
    42  		},
    43  		// http.NoBody means 0, not -1.
    44  		3: {
    45  			req:  &ClientRequest{Body: NoBody},
    46  			want: 0,
    47  		},
    48  	}
    49  	for i, tt := range tests {
    50  		got := actualContentLength(tt.req)
    51  		if got != tt.want {
    52  			t.Errorf("test[%d]: got %d; want %d", i, got, tt.want)
    53  		}
    54  	}
    55  }
    56  
    57  // Tests that gzipReader doesn't crash on a second Read call following
    58  // the first Read call's gzip.NewReader returning an error.
    59  func TestGzipReader_DoubleReadCrash(t *testing.T) {
    60  	gz := &gzipReader{
    61  		body: io.NopCloser(strings.NewReader("0123456789")),
    62  	}
    63  	var buf [1]byte
    64  	n, err1 := gz.Read(buf[:])
    65  	if n != 0 || !strings.Contains(fmt.Sprint(err1), "invalid header") {
    66  		t.Fatalf("Read = %v, %v; want 0, invalid header", n, err1)
    67  	}
    68  	n, err2 := gz.Read(buf[:])
    69  	if n != 0 || err2 != err1 {
    70  		t.Fatalf("second Read = %v, %v; want 0, %v", n, err2, err1)
    71  	}
    72  }
    73  
    74  func TestGzipReader_ReadAfterClose(t *testing.T) {
    75  	body := bytes.Buffer{}
    76  	w := gzip.NewWriter(&body)
    77  	w.Write([]byte("012345679"))
    78  	w.Close()
    79  	gz := &gzipReader{
    80  		body: io.NopCloser(&body),
    81  	}
    82  	var buf [1]byte
    83  	n, err := gz.Read(buf[:])
    84  	if n != 1 || err != nil {
    85  		t.Fatalf("first Read = %v, %v; want 1, nil", n, err)
    86  	}
    87  	if err := gz.Close(); err != nil {
    88  		t.Fatalf("gz Close error: %v", err)
    89  	}
    90  	n, err = gz.Read(buf[:])
    91  	if n != 0 || err != fs.ErrClosed {
    92  		t.Fatalf("Read after close = %v, %v; want 0, fs.ErrClosed", n, err)
    93  	}
    94  }
    95  
    96  func TestAuthorityAddr(t *testing.T) {
    97  	tests := []struct {
    98  		scheme, authority string
    99  		want              string
   100  	}{
   101  		{"http", "foo.com", "foo.com:80"},
   102  		{"https", "foo.com", "foo.com:443"},
   103  		{"https", "foo.com:", "foo.com:443"},
   104  		{"https", "foo.com:1234", "foo.com:1234"},
   105  		{"https", "1.2.3.4:1234", "1.2.3.4:1234"},
   106  		{"https", "1.2.3.4", "1.2.3.4:443"},
   107  		{"https", "1.2.3.4:", "1.2.3.4:443"},
   108  		{"https", "[::1]:1234", "[::1]:1234"},
   109  		{"https", "[::1]", "[::1]:443"},
   110  		{"https", "[::1]:", "[::1]:443"},
   111  	}
   112  	for _, tt := range tests {
   113  		got := authorityAddr(tt.scheme, tt.authority)
   114  		if got != tt.want {
   115  			t.Errorf("authorityAddr(%q, %q) = %q; want %q", tt.scheme, tt.authority, got, tt.want)
   116  		}
   117  	}
   118  }
   119  
   120  // Issue 25009: use Request.GetBody if present, even if it seems like
   121  // we might not need it. Apparently something else can still read from
   122  // the original request body. Data race? In any case, rewinding
   123  // unconditionally on retry is a nicer model anyway and should
   124  // simplify code in the future (after the Go 1.11 freeze)
   125  func TestTransportUsesGetBodyWhenPresent(t *testing.T) {
   126  	calls := 0
   127  	someBody := func() io.ReadCloser {
   128  		return struct{ io.ReadCloser }{io.NopCloser(bytes.NewReader(nil))}
   129  	}
   130  	req := &ClientRequest{
   131  		Body: someBody(),
   132  		GetBody: func() (io.ReadCloser, error) {
   133  			calls++
   134  			return someBody(), nil
   135  		},
   136  	}
   137  
   138  	req2, err := shouldRetryRequest(req, errClientConnUnusable)
   139  	if err != nil {
   140  		t.Fatal(err)
   141  	}
   142  	if calls != 1 {
   143  		t.Errorf("Calls = %d; want 1", calls)
   144  	}
   145  	if req2 == req {
   146  		t.Error("req2 changed")
   147  	}
   148  	if req2 == nil {
   149  		t.Fatal("req2 is nil")
   150  	}
   151  	if req2.Body == nil {
   152  		t.Fatal("req2.Body is nil")
   153  	}
   154  	if req2.GetBody == nil {
   155  		t.Fatal("req2.GetBody is nil")
   156  	}
   157  	if req2.Body == req.Body {
   158  		t.Error("req2.Body unchanged")
   159  	}
   160  }
   161  
   162  func TestClientConnTooIdle(t *testing.T) {
   163  	tests := []struct {
   164  		cc   func() *ClientConn
   165  		want bool
   166  	}{
   167  		{
   168  			func() *ClientConn {
   169  				return &ClientConn{idleTimeout: 5 * time.Second, lastIdle: time.Now().Add(-10 * time.Second)}
   170  			},
   171  			true,
   172  		},
   173  		{
   174  			func() *ClientConn {
   175  				return &ClientConn{idleTimeout: 5 * time.Second, lastIdle: time.Time{}}
   176  			},
   177  			false,
   178  		},
   179  		{
   180  			func() *ClientConn {
   181  				return &ClientConn{idleTimeout: 60 * time.Second, lastIdle: time.Now().Add(-10 * time.Second)}
   182  			},
   183  			false,
   184  		},
   185  		{
   186  			func() *ClientConn {
   187  				return &ClientConn{idleTimeout: 0, lastIdle: time.Now().Add(-10 * time.Second)}
   188  			},
   189  			false,
   190  		},
   191  	}
   192  	for i, tt := range tests {
   193  		got := tt.cc().tooIdleLocked()
   194  		if got != tt.want {
   195  			t.Errorf("%d. got %v; want %v", i, got, tt.want)
   196  		}
   197  	}
   198  }
   199  

View as plain text