Home | History | Annotate | Download | only in http
      1 // Copyright 2016 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 // White-box tests for transport.go (in package http instead of http_test).
      6 
      7 package http
      8 
      9 import (
     10 	"errors"
     11 	"net"
     12 	"strings"
     13 	"testing"
     14 )
     15 
     16 // Issue 15446: incorrect wrapping of errors when server closes an idle connection.
     17 func TestTransportPersistConnReadLoopEOF(t *testing.T) {
     18 	ln := newLocalListener(t)
     19 	defer ln.Close()
     20 
     21 	connc := make(chan net.Conn, 1)
     22 	go func() {
     23 		defer close(connc)
     24 		c, err := ln.Accept()
     25 		if err != nil {
     26 			t.Error(err)
     27 			return
     28 		}
     29 		connc <- c
     30 	}()
     31 
     32 	tr := new(Transport)
     33 	req, _ := NewRequest("GET", "http://"+ln.Addr().String(), nil)
     34 	req = req.WithT(t)
     35 	treq := &transportRequest{Request: req}
     36 	cm := connectMethod{targetScheme: "http", targetAddr: ln.Addr().String()}
     37 	pc, err := tr.getConn(treq, cm)
     38 	if err != nil {
     39 		t.Fatal(err)
     40 	}
     41 	defer pc.close(errors.New("test over"))
     42 
     43 	conn := <-connc
     44 	if conn == nil {
     45 		// Already called t.Error in the accept goroutine.
     46 		return
     47 	}
     48 	conn.Close() // simulate the server hanging up on the client
     49 
     50 	_, err = pc.roundTrip(treq)
     51 	if !isTransportReadFromServerError(err) && err != errServerClosedIdle {
     52 		t.Errorf("roundTrip = %#v, %v; want errServerClosedIdle or transportReadFromServerError", err, err)
     53 	}
     54 
     55 	<-pc.closech
     56 	err = pc.closed
     57 	if !isTransportReadFromServerError(err) && err != errServerClosedIdle {
     58 		t.Errorf("pc.closed = %#v, %v; want errServerClosedIdle or transportReadFromServerError", err, err)
     59 	}
     60 }
     61 
     62 func isTransportReadFromServerError(err error) bool {
     63 	_, ok := err.(transportReadFromServerError)
     64 	return ok
     65 }
     66 
     67 func newLocalListener(t *testing.T) net.Listener {
     68 	ln, err := net.Listen("tcp", "127.0.0.1:0")
     69 	if err != nil {
     70 		ln, err = net.Listen("tcp6", "[::1]:0")
     71 	}
     72 	if err != nil {
     73 		t.Fatal(err)
     74 	}
     75 	return ln
     76 }
     77 
     78 func dummyRequest(method string) *Request {
     79 	req, err := NewRequest(method, "http://fake.tld/", nil)
     80 	if err != nil {
     81 		panic(err)
     82 	}
     83 	return req
     84 }
     85 func dummyRequestWithBody(method string) *Request {
     86 	req, err := NewRequest(method, "http://fake.tld/", strings.NewReader("foo"))
     87 	if err != nil {
     88 		panic(err)
     89 	}
     90 	return req
     91 }
     92 
     93 func dummyRequestWithBodyNoGetBody(method string) *Request {
     94 	req := dummyRequestWithBody(method)
     95 	req.GetBody = nil
     96 	return req
     97 }
     98 
     99 // issue22091Error acts like a golang.org/x/net/http2.ErrNoCachedConn.
    100 type issue22091Error struct{}
    101 
    102 func (issue22091Error) IsHTTP2NoCachedConnError() {}
    103 func (issue22091Error) Error() string             { return "issue22091Error" }
    104 
    105 func TestTransportShouldRetryRequest(t *testing.T) {
    106 	tests := []struct {
    107 		pc  *persistConn
    108 		req *Request
    109 
    110 		err  error
    111 		want bool
    112 	}{
    113 		0: {
    114 			pc:   &persistConn{reused: false},
    115 			req:  dummyRequest("POST"),
    116 			err:  nothingWrittenError{},
    117 			want: false,
    118 		},
    119 		1: {
    120 			pc:   &persistConn{reused: true},
    121 			req:  dummyRequest("POST"),
    122 			err:  nothingWrittenError{},
    123 			want: true,
    124 		},
    125 		2: {
    126 			pc:   &persistConn{reused: true},
    127 			req:  dummyRequest("POST"),
    128 			err:  http2ErrNoCachedConn,
    129 			want: true,
    130 		},
    131 		3: {
    132 			pc:   nil,
    133 			req:  nil,
    134 			err:  issue22091Error{}, // like an external http2ErrNoCachedConn
    135 			want: true,
    136 		},
    137 		4: {
    138 			pc:   &persistConn{reused: true},
    139 			req:  dummyRequest("POST"),
    140 			err:  errMissingHost,
    141 			want: false,
    142 		},
    143 		5: {
    144 			pc:   &persistConn{reused: true},
    145 			req:  dummyRequest("POST"),
    146 			err:  transportReadFromServerError{},
    147 			want: false,
    148 		},
    149 		6: {
    150 			pc:   &persistConn{reused: true},
    151 			req:  dummyRequest("GET"),
    152 			err:  transportReadFromServerError{},
    153 			want: true,
    154 		},
    155 		7: {
    156 			pc:   &persistConn{reused: true},
    157 			req:  dummyRequest("GET"),
    158 			err:  errServerClosedIdle,
    159 			want: true,
    160 		},
    161 		8: {
    162 			pc:   &persistConn{reused: true},
    163 			req:  dummyRequestWithBody("POST"),
    164 			err:  nothingWrittenError{},
    165 			want: true,
    166 		},
    167 		9: {
    168 			pc:   &persistConn{reused: true},
    169 			req:  dummyRequestWithBodyNoGetBody("POST"),
    170 			err:  nothingWrittenError{},
    171 			want: false,
    172 		},
    173 	}
    174 	for i, tt := range tests {
    175 		got := tt.pc.shouldRetryRequest(tt.req, tt.err)
    176 		if got != tt.want {
    177 			t.Errorf("%d. shouldRetryRequest = %v; want %v", i, got, tt.want)
    178 		}
    179 	}
    180 }
    181