Commit 6df4c3a4 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

net/http: document that Client methods always return *url.Error

Updates #9424

Change-Id: If117ba3e7d031f84b30d3a721ef99fe622734de2
Reviewed-on: https://go-review.googlesource.com/125575Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent 416676f4
...@@ -357,7 +357,9 @@ func basicAuth(username, password string) string { ...@@ -357,7 +357,9 @@ func basicAuth(username, password string) string {
// //
// An error is returned if there were too many redirects or if there // An error is returned if there were too many redirects or if there
// was an HTTP protocol error. A non-2xx response doesn't cause an // was an HTTP protocol error. A non-2xx response doesn't cause an
// error. // error. Any returned error will be of type *url.Error. The url.Error
// value's Timeout method will report true if request timed out or was
// canceled.
// //
// When err is nil, resp always contains a non-nil resp.Body. // When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it. // Caller should close resp.Body when done reading from it.
...@@ -382,7 +384,9 @@ func Get(url string) (resp *Response, err error) { ...@@ -382,7 +384,9 @@ func Get(url string) (resp *Response, err error) {
// //
// An error is returned if the Client's CheckRedirect function fails // An error is returned if the Client's CheckRedirect function fails
// or if there was an HTTP protocol error. A non-2xx response doesn't // or if there was an HTTP protocol error. A non-2xx response doesn't
// cause an error. // cause an error. Any returned error will be of type *url.Error. The
// url.Error value's Timeout method will report true if request timed
// out or was canceled.
// //
// When err is nil, resp always contains a non-nil resp.Body. // When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it. // Caller should close resp.Body when done reading from it.
...@@ -457,6 +461,15 @@ func redirectBehavior(reqMethod string, resp *Response, ireq *Request) (redirect ...@@ -457,6 +461,15 @@ func redirectBehavior(reqMethod string, resp *Response, ireq *Request) (redirect
return redirectMethod, shouldRedirect, includeBody return redirectMethod, shouldRedirect, includeBody
} }
// urlErrorOp returns the (*url.Error).Op value to use for the
// provided (*Request).Method value.
func urlErrorOp(method string) string {
if method == "" {
return "Get"
}
return method[:1] + strings.ToLower(method[1:])
}
// Do sends an HTTP request and returns an HTTP response, following // Do sends an HTTP request and returns an HTTP response, following
// policy (such as redirects, cookies, auth) as configured on the // policy (such as redirects, cookies, auth) as configured on the
// client. // client.
...@@ -490,10 +503,26 @@ func redirectBehavior(reqMethod string, resp *Response, ireq *Request) (redirect ...@@ -490,10 +503,26 @@ func redirectBehavior(reqMethod string, resp *Response, ireq *Request) (redirect
// provided that the Request.GetBody function is defined. // provided that the Request.GetBody function is defined.
// The NewRequest function automatically sets GetBody for common // The NewRequest function automatically sets GetBody for common
// standard library body types. // standard library body types.
//
// Any returned error will be of type *url.Error. The url.Error
// value's Timeout method will report true if request timed out or was
// canceled.
func (c *Client) Do(req *Request) (*Response, error) { func (c *Client) Do(req *Request) (*Response, error) {
return c.do(req)
}
var testHookClientDoResult func(retres *Response, reterr error)
func (c *Client) do(req *Request) (retres *Response, reterr error) {
if testHookClientDoResult != nil {
defer func() { testHookClientDoResult(retres, reterr) }()
}
if req.URL == nil { if req.URL == nil {
req.closeBody() req.closeBody()
return nil, errors.New("http: nil Request.URL") return nil, &url.Error{
Op: urlErrorOp(req.Method),
Err: errors.New("http: nil Request.URL"),
}
} }
var ( var (
...@@ -512,7 +541,6 @@ func (c *Client) Do(req *Request) (*Response, error) { ...@@ -512,7 +541,6 @@ func (c *Client) Do(req *Request) (*Response, error) {
if !reqBodyClosed { if !reqBodyClosed {
req.closeBody() req.closeBody()
} }
method := valueOrDefault(reqs[0].Method, "GET")
var urlStr string var urlStr string
if resp != nil && resp.Request != nil { if resp != nil && resp.Request != nil {
urlStr = stripPassword(resp.Request.URL) urlStr = stripPassword(resp.Request.URL)
...@@ -520,7 +548,7 @@ func (c *Client) Do(req *Request) (*Response, error) { ...@@ -520,7 +548,7 @@ func (c *Client) Do(req *Request) (*Response, error) {
urlStr = stripPassword(req.URL) urlStr = stripPassword(req.URL)
} }
return &url.Error{ return &url.Error{
Op: method[:1] + strings.ToLower(method[1:]), Op: urlErrorOp(reqs[0].Method),
URL: urlStr, URL: urlStr,
Err: err, Err: err,
} }
...@@ -617,6 +645,7 @@ func (c *Client) Do(req *Request) (*Response, error) { ...@@ -617,6 +645,7 @@ func (c *Client) Do(req *Request) (*Response, error) {
reqBodyClosed = true reqBodyClosed = true
if !deadline.IsZero() && didTimeout() { if !deadline.IsZero() && didTimeout() {
err = &httpError{ err = &httpError{
// TODO: early in cycle: s/Client.Timeout exceeded/timeout or context cancelation/
err: err.Error() + " (Client.Timeout exceeded while awaiting headers)", err: err.Error() + " (Client.Timeout exceeded while awaiting headers)",
timeout: true, timeout: true,
} }
...@@ -827,6 +856,7 @@ func (b *cancelTimerBody) Read(p []byte) (n int, err error) { ...@@ -827,6 +856,7 @@ func (b *cancelTimerBody) Read(p []byte) (n int, err error) {
} }
if b.reqDidTimeout() { if b.reqDidTimeout() {
err = &httpError{ err = &httpError{
// TODO: early in cycle: s/Client.Timeout exceeded/timeout or context cancelation/
err: err.Error() + " (Client.Timeout exceeded while reading body)", err: err.Error() + " (Client.Timeout exceeded while reading body)",
timeout: true, timeout: true,
} }
......
...@@ -9,7 +9,9 @@ package http ...@@ -9,7 +9,9 @@ package http
import ( import (
"context" "context"
"fmt"
"net" "net"
"net/url"
"sort" "sort"
"sync" "sync"
"testing" "testing"
...@@ -40,6 +42,21 @@ func init() { ...@@ -40,6 +42,21 @@ func init() {
// When not under test, these values are always nil // When not under test, these values are always nil
// and never assigned to. // and never assigned to.
testHookMu = new(sync.Mutex) testHookMu = new(sync.Mutex)
testHookClientDoResult = func(res *Response, err error) {
if err != nil {
if _, ok := err.(*url.Error); !ok {
panic(fmt.Sprintf("unexpected Client.Do error of type %T; want *url.Error", err))
}
} else {
if res == nil {
panic("Client.Do returned nil, nil")
}
if res.Body == nil {
panic("Client.Do returned nil res.Body and no error")
}
}
}
} }
var ( var (
......
...@@ -10,6 +10,9 @@ package http ...@@ -10,6 +10,9 @@ package http
// //
// For higher-level HTTP client support (such as handling of cookies // For higher-level HTTP client support (such as handling of cookies
// and redirects), see Get, Post, and the Client type. // and redirects), see Get, Post, and the Client type.
//
// Like the RoundTripper interface, the error types returned
// by RoundTrip are unspecified.
func (t *Transport) RoundTrip(req *Request) (*Response, error) { func (t *Transport) RoundTrip(req *Request) (*Response, error) {
return t.roundTrip(req) return t.roundTrip(req)
} }
...@@ -2426,7 +2426,7 @@ func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { ...@@ -2426,7 +2426,7 @@ func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
// connections and they were configured with "h2" in the TLS // connections and they were configured with "h2" in the TLS
// Config.NextProtos. // Config.NextProtos.
// //
// Serve always returns a non-nil reror. // Serve always returns a non-nil error.
func Serve(l net.Listener, handler Handler) error { func Serve(l net.Listener, handler Handler) error {
srv := &Server{Handler: handler} srv := &Server{Handler: handler}
return srv.Serve(l) return srv.Serve(l)
......
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