Commit 194a5c3e authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

net/http: update bundled http2, add test for Transport's User-Agent behavior

Adds a test that both http1 and http2's Transport send a default
User-Agent, with the same behavior.

Updates bundled http2 to golang.org/x/net git rev 1ade16a545 (for
https://go-review.googlesource.com/18285)

The http1 behavior changes slightly: if req.Header["User-Agent"] is
defined at all, even if it's nil or a zero-length slice, then the
User-Agent header is omitted. This is a slight behavior change for
http1, but is consistent with how http1 & http2 do optional headers
elsewhere (such as "Date", "Content-Type"). The old behavior (set it
explicitly to "", aka []string{""}) still works as before. And now
there are even tests.

Fixes #13685

Change-Id: I5786a6913b560de4a5f1f90e595fe320ff567adf
Reviewed-on: https://go-review.googlesource.com/18284
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent a4f10bdd
...@@ -734,3 +734,64 @@ func testConnectRequest(t *testing.T, h2 bool) { ...@@ -734,3 +734,64 @@ func testConnectRequest(t *testing.T, h2 bool) {
} }
} }
} }
func TestTransportUserAgent_h1(t *testing.T) { testTransportUserAgent(t, h1Mode) }
func TestTransportUserAgent_h2(t *testing.T) { testTransportUserAgent(t, h2Mode) }
func testTransportUserAgent(t *testing.T, h2 bool) {
defer afterTest(t)
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
fmt.Fprintf(w, "%q", r.Header["User-Agent"])
}))
defer cst.close()
either := func(a, b string) string {
if h2 {
return b
}
return a
}
tests := []struct {
setup func(*Request)
want string
}{
{
func(r *Request) {},
either(`["Go-http-client/1.1"]`, `["Go-http-client/2.0"]`),
},
{
func(r *Request) { r.Header.Set("User-Agent", "foo/1.2.3") },
`["foo/1.2.3"]`,
},
{
func(r *Request) { r.Header["User-Agent"] = []string{"single", "or", "multiple"} },
`["single"]`,
},
{
func(r *Request) { r.Header.Set("User-Agent", "") },
`[]`,
},
{
func(r *Request) { r.Header["User-Agent"] = nil },
`[]`,
},
}
for i, tt := range tests {
req, _ := NewRequest("GET", cst.ts.URL, nil)
tt.setup(req)
res, err := cst.c.Do(req)
if err != nil {
t.Errorf("%d. RoundTrip = %v", i, err)
continue
}
slurp, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Errorf("%d. read body = %v", i, err)
continue
}
if string(slurp) != tt.want {
t.Errorf("%d. body mismatch.\n got: %s\nwant: %s\n", i, slurp, tt.want)
}
}
}
...@@ -24,7 +24,6 @@ import ( ...@@ -24,7 +24,6 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"golang.org/x/net/http2/hpack"
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
...@@ -38,6 +37,8 @@ import ( ...@@ -38,6 +37,8 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"golang.org/x/net/http2/hpack"
) )
// ClientConnPool manages a pool of HTTP/2 client connections. // ClientConnPool manages a pool of HTTP/2 client connections.
...@@ -4095,6 +4096,8 @@ const ( ...@@ -4095,6 +4096,8 @@ const (
// transportDefaultStreamMinRefresh is the minimum number of bytes we'll send // transportDefaultStreamMinRefresh is the minimum number of bytes we'll send
// a stream-level WINDOW_UPDATE for at a time. // a stream-level WINDOW_UPDATE for at a time.
http2transportDefaultStreamMinRefresh = 4 << 10 http2transportDefaultStreamMinRefresh = 4 << 10
http2defaultUserAgent = "Go-http-client/2.0"
) )
// Transport is an HTTP/2 Transport. // Transport is an HTTP/2 Transport.
...@@ -4794,11 +4797,23 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail ...@@ -4794,11 +4797,23 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail
cc.writeHeader("trailer", trailers) cc.writeHeader("trailer", trailers)
} }
var didUA bool
for k, vv := range req.Header { for k, vv := range req.Header {
lowKey := strings.ToLower(k) lowKey := strings.ToLower(k)
if lowKey == "host" { if lowKey == "host" {
continue continue
} }
if lowKey == "user-agent" {
didUA = true
if len(vv) < 1 {
continue
}
vv = vv[:1]
if vv[0] == "" {
continue
}
}
for _, v := range vv { for _, v := range vv {
cc.writeHeader(lowKey, v) cc.writeHeader(lowKey, v)
} }
...@@ -4806,6 +4821,9 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail ...@@ -4806,6 +4821,9 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail
if addGzipHeader { if addGzipHeader {
cc.writeHeader("accept-encoding", "gzip") cc.writeHeader("accept-encoding", "gzip")
} }
if !didUA {
cc.writeHeader("user-agent", http2defaultUserAgent)
}
return cc.hbuf.Bytes() return cc.hbuf.Bytes()
} }
......
...@@ -427,10 +427,8 @@ func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, wai ...@@ -427,10 +427,8 @@ func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, wai
// Use the defaultUserAgent unless the Header contains one, which // Use the defaultUserAgent unless the Header contains one, which
// may be blank to not send the header. // may be blank to not send the header.
userAgent := defaultUserAgent userAgent := defaultUserAgent
if req.Header != nil { if _, ok := req.Header["User-Agent"]; ok {
if ua := req.Header["User-Agent"]; len(ua) > 0 { userAgent = req.Header.Get("User-Agent")
userAgent = ua[0]
}
} }
if userAgent != "" { if userAgent != "" {
_, err = fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent) _, err = fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment