1
0
mirror of https://github.com/golang/go synced 2024-09-29 20:24:34 -06:00

net/http: add func NewRequestWithContext, Request.Clone

Fixes #23544

Change-Id: Iaa31d76c4cda8ce22412d73c9025fc57e4fb1967
Reviewed-on: https://go-review.googlesource.com/c/go/+/174324
Reviewed-by: Andrew Bonventre <andybons@golang.org>
This commit is contained in:
Brad Fitzpatrick 2019-04-29 17:57:10 +00:00
parent 5e404b3620
commit f5c43b9194
4 changed files with 122 additions and 21 deletions

View File

@ -317,8 +317,7 @@ func TestClientRedirectContext(t *testing.T) {
return errors.New("redirected request's context never expired after root request canceled")
}
}
req, _ := NewRequest("GET", ts.URL, nil)
req = req.WithContext(ctx)
req, _ := NewRequestWithContext(ctx, "GET", ts.URL, nil)
_, err := c.Do(req)
ue, ok := err.(*url.Error)
if !ok {

64
src/net/http/clone.go Normal file
View File

@ -0,0 +1,64 @@
// Copyright 2019 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
import (
"mime/multipart"
"net/textproto"
"net/url"
)
func cloneURLValues(v url.Values) url.Values {
if v == nil {
return nil
}
// http.Header and url.Values have the same representation, so temporarily
// treat it like http.Header, which does have a clone:
return url.Values(Header(v).Clone())
}
func cloneURL(u *url.URL) *url.URL {
if u == nil {
return nil
}
u2 := new(url.URL)
*u2 = *u
if u.User != nil {
u2.User = new(url.Userinfo)
*u2.User = *u.User
}
return u2
}
func cloneMultipartForm(f *multipart.Form) *multipart.Form {
if f == nil {
return nil
}
f2 := &multipart.Form{
Value: (map[string][]string)(Header(f.Value).Clone()),
}
if f.File != nil {
m := make(map[string][]*multipart.FileHeader)
for k, vv := range f.File {
vv2 := make([]*multipart.FileHeader, len(vv))
for i, v := range vv {
vv2[i] = cloneMultipartFileHeader(v)
}
m[k] = vv2
}
f2.File = m
}
return f2
}
func cloneMultipartFileHeader(fh *multipart.FileHeader) *multipart.FileHeader {
if fh == nil {
return nil
}
fh2 := new(multipart.FileHeader)
*fh2 = *fh
fh2.Header = textproto.MIMEHeader(Header(fh.Header).Clone())
return fh2
}

View File

@ -196,13 +196,11 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}()
}
outreq := req.WithContext(ctx) // includes shallow copies of maps, but okay
outreq := req.Clone(ctx)
if req.ContentLength == 0 {
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
}
outreq.Header = req.Header.Clone()
p.Director(outreq)
outreq.Close = false

View File

@ -304,7 +304,7 @@ type Request struct {
//
// For server requests, this field is not applicable.
//
// Deprecated: Use the Context and WithContext methods
// Deprecated: Set the Request's context with NewRequestWithContext
// instead. If a Request's Cancel field and context are both
// set, it is undefined whether Cancel is respected.
Cancel <-chan struct{}
@ -345,6 +345,11 @@ func (r *Request) Context() context.Context {
// For outgoing client request, the context controls the entire
// lifetime of a request and its response: obtaining a connection,
// sending the request, and reading the response headers and body.
//
// To create a new request with a context, use NewRequestWithContext.
// To change the context of a request (such as an incoming) you then
// also want to modify to send back out, use Request.Clone. Between
// those two uses, it's rare to need WithContext.
func (r *Request) WithContext(ctx context.Context) *Request {
if ctx == nil {
panic("nil context")
@ -352,16 +357,38 @@ func (r *Request) WithContext(ctx context.Context) *Request {
r2 := new(Request)
*r2 = *r
r2.ctx = ctx
r2.URL = cloneURL(r.URL) // legacy behavior; TODO: try to remove. Issue 23544
return r2
}
// Deep copy the URL because it isn't
// a map and the URL is mutable by users
// of WithContext.
if r.URL != nil {
r2URL := new(url.URL)
*r2URL = *r.URL
r2.URL = r2URL
// Clone returns a deep copy of r with its context changed to ctx.
// The provided ctx must be non-nil.
//
// For an outgoing client request, the context controls the entire
// lifetime of a request and its response: obtaining a connection,
// sending the request, and reading the response headers and body.
func (r *Request) Clone(ctx context.Context) *Request {
if ctx == nil {
panic("nil context")
}
r2 := new(Request)
*r2 = *r
r2.ctx = ctx
r2.URL = cloneURL(r.URL)
if r.Header != nil {
r2.Header = r.Header.Clone()
}
if r.Trailer != nil {
r2.Trailer = r.Trailer.Clone()
}
if s := r.TransferEncoding; s != nil {
s2 := make([]string, len(s))
copy(s2, s)
r2.TransferEncoding = s
}
r2.Form = cloneURLValues(r.Form)
r2.PostForm = cloneURLValues(r.PostForm)
r2.MultipartForm = cloneMultipartForm(r.MultipartForm)
return r2
}
@ -781,25 +808,34 @@ func validMethod(method string) bool {
return len(method) > 0 && strings.IndexFunc(method, isNotToken) == -1
}
// NewRequest returns a new Request given a method, URL, and optional body.
// NewRequest wraps NewRequestWithContext using the background context.
func NewRequest(method, url string, body io.Reader) (*Request, error) {
return NewRequestWithContext(context.Background(), method, url, body)
}
// NewRequestWithContext returns a new Request given a method, URL, and
// optional body.
//
// If the provided body is also an io.Closer, the returned
// Request.Body is set to body and will be closed by the Client
// methods Do, Post, and PostForm, and Transport.RoundTrip.
//
// NewRequest returns a Request suitable for use with Client.Do or
// Transport.RoundTrip. To create a request for use with testing a
// Server Handler, either use the NewRequest function in the
// NewRequestWithContext returns a Request suitable for use with
// Client.Do or Transport.RoundTrip. To create a request for use with
// testing a Server Handler, either use the NewRequest function in the
// net/http/httptest package, use ReadRequest, or manually update the
// Request fields. See the Request type's documentation for the
// difference between inbound and outbound request fields.
// Request fields. For an outgoing client request, the context
// controls the entire lifetime of a request and its response:
// obtaining a connection, sending the request, and reading the
// response headers and body. See the Request type's documentation for
// the difference between inbound and outbound request fields.
//
// If body is of type *bytes.Buffer, *bytes.Reader, or
// *strings.Reader, the returned request's ContentLength is set to its
// exact value (instead of -1), GetBody is populated (so 307 and 308
// redirects can replay the body), and Body is set to NoBody if the
// ContentLength is 0.
func NewRequest(method, url string, body io.Reader) (*Request, error) {
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
if method == "" {
// We document that "" means "GET" for Request.Method, and people have
// relied on that from NewRequest, so keep that working.
@ -809,6 +845,9 @@ func NewRequest(method, url string, body io.Reader) (*Request, error) {
if !validMethod(method) {
return nil, fmt.Errorf("net/http: invalid method %q", method)
}
if ctx == nil {
return nil, errors.New("net/http: nil Context")
}
u, err := parseURL(url) // Just url.Parse (url is shadowed for godoc).
if err != nil {
return nil, err
@ -820,6 +859,7 @@ func NewRequest(method, url string, body io.Reader) (*Request, error) {
// The host's colon:port should be normalized. See Issue 14836.
u.Host = removeEmptyPort(u.Host)
req := &Request{
ctx: ctx,
Method: method,
URL: u,
Proto: "HTTP/1.1",