mirror of
https://github.com/golang/net.git
synced 2026-03-31 18:37:08 +09:00
This change makes it easier to move x/net/http2 into std. Moving the http2 package into std and importing it from net/http (rather than bundling it as net/http/h2_bundle.go) requires removing the http2->net/http dependency. Moving tests into the http2_test package allows them to continue importing net/http without creating a cycle. Change-Id: If0799a94a6d2c90f02d7f391e352e14e6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/net/+/749280 Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Nicholas Husin <husin@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Nicholas Husin <nsh@golang.org>
294 lines
6.9 KiB
Go
294 lines
6.9 KiB
Go
// 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 http2
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"net/http"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type panicReader struct{}
|
|
|
|
func (panicReader) Read([]byte) (int, error) { panic("unexpected Read") }
|
|
func (panicReader) Close() error { panic("unexpected Close") }
|
|
|
|
func TestActualContentLength(t *testing.T) {
|
|
tests := []struct {
|
|
req *http.Request
|
|
want int64
|
|
}{
|
|
// Verify we don't read from Body:
|
|
0: {
|
|
req: &http.Request{Body: panicReader{}},
|
|
want: -1,
|
|
},
|
|
// nil Body means 0, regardless of ContentLength:
|
|
1: {
|
|
req: &http.Request{Body: nil, ContentLength: 5},
|
|
want: 0,
|
|
},
|
|
// ContentLength is used if set.
|
|
2: {
|
|
req: &http.Request{Body: panicReader{}, ContentLength: 5},
|
|
want: 5,
|
|
},
|
|
// http.NoBody means 0, not -1.
|
|
3: {
|
|
req: &http.Request{Body: http.NoBody},
|
|
want: 0,
|
|
},
|
|
}
|
|
for i, tt := range tests {
|
|
got := actualContentLength(tt.req)
|
|
if got != tt.want {
|
|
t.Errorf("test[%d]: got %d; want %d", i, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests that gzipReader doesn't crash on a second Read call following
|
|
// the first Read call's gzip.NewReader returning an error.
|
|
func TestGzipReader_DoubleReadCrash(t *testing.T) {
|
|
gz := &gzipReader{
|
|
body: io.NopCloser(strings.NewReader("0123456789")),
|
|
}
|
|
var buf [1]byte
|
|
n, err1 := gz.Read(buf[:])
|
|
if n != 0 || !strings.Contains(fmt.Sprint(err1), "invalid header") {
|
|
t.Fatalf("Read = %v, %v; want 0, invalid header", n, err1)
|
|
}
|
|
n, err2 := gz.Read(buf[:])
|
|
if n != 0 || err2 != err1 {
|
|
t.Fatalf("second Read = %v, %v; want 0, %v", n, err2, err1)
|
|
}
|
|
}
|
|
|
|
func TestGzipReader_ReadAfterClose(t *testing.T) {
|
|
body := bytes.Buffer{}
|
|
w := gzip.NewWriter(&body)
|
|
w.Write([]byte("012345679"))
|
|
w.Close()
|
|
gz := &gzipReader{
|
|
body: io.NopCloser(&body),
|
|
}
|
|
var buf [1]byte
|
|
n, err := gz.Read(buf[:])
|
|
if n != 1 || err != nil {
|
|
t.Fatalf("first Read = %v, %v; want 1, nil", n, err)
|
|
}
|
|
if err := gz.Close(); err != nil {
|
|
t.Fatalf("gz Close error: %v", err)
|
|
}
|
|
n, err = gz.Read(buf[:])
|
|
if n != 0 || err != fs.ErrClosed {
|
|
t.Fatalf("Read after close = %v, %v; want 0, fs.ErrClosed", n, err)
|
|
}
|
|
}
|
|
|
|
func TestTransportNewTLSConfig(t *testing.T) {
|
|
tests := [...]struct {
|
|
conf *tls.Config
|
|
host string
|
|
want *tls.Config
|
|
}{
|
|
// Normal case.
|
|
0: {
|
|
conf: nil,
|
|
host: "foo.com",
|
|
want: &tls.Config{
|
|
ServerName: "foo.com",
|
|
NextProtos: []string{NextProtoTLS},
|
|
},
|
|
},
|
|
|
|
// User-provided name (bar.com) takes precedence:
|
|
1: {
|
|
conf: &tls.Config{
|
|
ServerName: "bar.com",
|
|
},
|
|
host: "foo.com",
|
|
want: &tls.Config{
|
|
ServerName: "bar.com",
|
|
NextProtos: []string{NextProtoTLS},
|
|
},
|
|
},
|
|
|
|
// NextProto is prepended:
|
|
2: {
|
|
conf: &tls.Config{
|
|
NextProtos: []string{"foo", "bar"},
|
|
},
|
|
host: "example.com",
|
|
want: &tls.Config{
|
|
ServerName: "example.com",
|
|
NextProtos: []string{NextProtoTLS, "foo", "bar"},
|
|
},
|
|
},
|
|
|
|
// NextProto is not duplicated:
|
|
3: {
|
|
conf: &tls.Config{
|
|
NextProtos: []string{"foo", "bar", NextProtoTLS},
|
|
},
|
|
host: "example.com",
|
|
want: &tls.Config{
|
|
ServerName: "example.com",
|
|
NextProtos: []string{"foo", "bar", NextProtoTLS},
|
|
},
|
|
},
|
|
}
|
|
for i, tt := range tests {
|
|
// Ignore the session ticket keys part, which ends up populating
|
|
// unexported fields in the Config:
|
|
if tt.conf != nil {
|
|
tt.conf.SessionTicketsDisabled = true
|
|
}
|
|
|
|
tr := &Transport{TLSClientConfig: tt.conf}
|
|
got := tr.newTLSConfig(tt.host)
|
|
|
|
got.SessionTicketsDisabled = false
|
|
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("%d. got %#v; want %#v", i, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAuthorityAddr(t *testing.T) {
|
|
tests := []struct {
|
|
scheme, authority string
|
|
want string
|
|
}{
|
|
{"http", "foo.com", "foo.com:80"},
|
|
{"https", "foo.com", "foo.com:443"},
|
|
{"https", "foo.com:", "foo.com:443"},
|
|
{"https", "foo.com:1234", "foo.com:1234"},
|
|
{"https", "1.2.3.4:1234", "1.2.3.4:1234"},
|
|
{"https", "1.2.3.4", "1.2.3.4:443"},
|
|
{"https", "1.2.3.4:", "1.2.3.4:443"},
|
|
{"https", "[::1]:1234", "[::1]:1234"},
|
|
{"https", "[::1]", "[::1]:443"},
|
|
{"https", "[::1]:", "[::1]:443"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := authorityAddr(tt.scheme, tt.authority)
|
|
if got != tt.want {
|
|
t.Errorf("authorityAddr(%q, %q) = %q; want %q", tt.scheme, tt.authority, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Issue 25009: use Request.GetBody if present, even if it seems like
|
|
// we might not need it. Apparently something else can still read from
|
|
// the original request body. Data race? In any case, rewinding
|
|
// unconditionally on retry is a nicer model anyway and should
|
|
// simplify code in the future (after the Go 1.11 freeze)
|
|
func TestTransportUsesGetBodyWhenPresent(t *testing.T) {
|
|
calls := 0
|
|
someBody := func() io.ReadCloser {
|
|
return struct{ io.ReadCloser }{io.NopCloser(bytes.NewReader(nil))}
|
|
}
|
|
req := &http.Request{
|
|
Body: someBody(),
|
|
GetBody: func() (io.ReadCloser, error) {
|
|
calls++
|
|
return someBody(), nil
|
|
},
|
|
}
|
|
|
|
req2, err := shouldRetryRequest(req, errClientConnUnusable)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if calls != 1 {
|
|
t.Errorf("Calls = %d; want 1", calls)
|
|
}
|
|
if req2 == req {
|
|
t.Error("req2 changed")
|
|
}
|
|
if req2 == nil {
|
|
t.Fatal("req2 is nil")
|
|
}
|
|
if req2.Body == nil {
|
|
t.Fatal("req2.Body is nil")
|
|
}
|
|
if req2.GetBody == nil {
|
|
t.Fatal("req2.GetBody is nil")
|
|
}
|
|
if req2.Body == req.Body {
|
|
t.Error("req2.Body unchanged")
|
|
}
|
|
}
|
|
|
|
// Issue 22891: verify that the "https" altproto we register with net/http
|
|
// is a certain type: a struct with one field with our *http2.Transport in it.
|
|
func TestNoDialH2RoundTripperType(t *testing.T) {
|
|
t1 := new(http.Transport)
|
|
t2 := new(Transport)
|
|
rt := noDialH2RoundTripper{t2}
|
|
if err := registerHTTPSProtocol(t1, rt); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rv := reflect.ValueOf(rt)
|
|
if rv.Type().Kind() != reflect.Struct {
|
|
t.Fatalf("kind = %v; net/http expects struct", rv.Type().Kind())
|
|
}
|
|
if n := rv.Type().NumField(); n != 1 {
|
|
t.Fatalf("fields = %d; net/http expects 1", n)
|
|
}
|
|
v := rv.Field(0)
|
|
if _, ok := v.Interface().(*Transport); !ok {
|
|
t.Fatalf("wrong kind %T; want *Transport", v.Interface())
|
|
}
|
|
}
|
|
|
|
func TestClientConnTooIdle(t *testing.T) {
|
|
tests := []struct {
|
|
cc func() *ClientConn
|
|
want bool
|
|
}{
|
|
{
|
|
func() *ClientConn {
|
|
return &ClientConn{idleTimeout: 5 * time.Second, lastIdle: time.Now().Add(-10 * time.Second)}
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
func() *ClientConn {
|
|
return &ClientConn{idleTimeout: 5 * time.Second, lastIdle: time.Time{}}
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
func() *ClientConn {
|
|
return &ClientConn{idleTimeout: 60 * time.Second, lastIdle: time.Now().Add(-10 * time.Second)}
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
func() *ClientConn {
|
|
return &ClientConn{idleTimeout: 0, lastIdle: time.Now().Add(-10 * time.Second)}
|
|
},
|
|
false,
|
|
},
|
|
}
|
|
for i, tt := range tests {
|
|
got := tt.cc().tooIdleLocked()
|
|
if got != tt.want {
|
|
t.Errorf("%d. got %v; want %v", i, got, tt.want)
|
|
}
|
|
}
|
|
}
|