b := strings.NewReader(*post)
r, err = http.Post(url, "application/x-www-form-urlencoded", b)
} else {
- r, _, err = http.Get(url)
+ r, err = http.Get(url)
}
if err != nil {
log.Fatal(err)
}
cmd += "?" + http.EncodeQuery(m)
}
- r, _, err = http.Get(cmd)
+ r, err = http.Get(cmd)
case "POST":
r, err = http.PostForm(cmd, args)
default:
// remote search
for _, addr := range addrs {
url := "http://" + addr + search
- res, _, err = http.Get(url)
+ res, err = http.Get(url)
if err == nil && res.StatusCode == http.StatusOK {
break
}
netdial.go\
main.go\
osopen.go\
+ httpfinalurl.go\
httpserver.go\
procattr.go\
reflect.go\
--- /dev/null
+// Copyright 2011 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 main
+
+import (
+ "go/ast"
+)
+
+var httpFinalURLFix = fix{
+ "httpfinalurl",
+ httpfinalurl,
+ `Adapt http Get calls to not have a finalURL result parameter.
+
+ http://codereview.appspot.com/4535056/
+`,
+}
+
+func init() {
+ register(httpFinalURLFix)
+}
+
+func httpfinalurl(f *ast.File) bool {
+ if !imports(f, "http") {
+ return false
+ }
+
+ fixed := false
+ walk(f, func(n interface{}) {
+ // Fix up calls to http.Get.
+ //
+ // If they have blank identifiers, remove them:
+ // resp, _, err := http.Get(url)
+ // -> resp, err := http.Get(url)
+ //
+ // But if they're using the finalURL parameter, warn:
+ // resp, finalURL, err := http.Get(url)
+ as, ok := n.(*ast.AssignStmt)
+ if !ok || len(as.Lhs) != 3 || len(as.Rhs) != 1 {
+ return
+ }
+
+ if !isCall(as.Rhs[0], "http", "Get") {
+ return
+ }
+
+ if isBlank(as.Lhs[1]) {
+ as.Lhs = []ast.Expr{as.Lhs[0], as.Lhs[2]}
+ fixed = true
+ } else {
+ warn(as.Pos(), "call to http.Get records final URL")
+ }
+ })
+ return fixed
+}
--- /dev/null
+// Copyright 2011 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 main
+
+func init() {
+ addTestCases(httpfinalurlTests)
+}
+
+var httpfinalurlTests = []testCase{
+ {
+ Name: "finalurl.0",
+ In: `package main
+
+import (
+ "http"
+)
+
+func f() {
+ resp, _, err := http.Get("http://www.google.com/")
+ _, _ = resp, err
+}
+`,
+ Out: `package main
+
+import (
+ "http"
+)
+
+func f() {
+ resp, err := http.Get("http://www.google.com/")
+ _, _ = resp, err
+}
+`,
+ },
+}
// 303 (See Other)
// 307 (Temporary Redirect)
//
-// finalURL is the URL from which the response was fetched -- identical to the
-// input URL unless redirects were followed.
-//
// Caller should close r.Body when done reading from it.
//
// Get is a convenience wrapper around DefaultClient.Get.
-func Get(url string) (r *Response, finalURL string, err os.Error) {
+func Get(url string) (r *Response, err os.Error) {
return DefaultClient.Get(url)
}
// 303 (See Other)
// 307 (Temporary Redirect)
//
-// finalURL is the URL from which the response was fetched -- identical
-// to the input URL unless redirects were followed.
-//
// Caller should close r.Body when done reading from it.
-func (c *Client) Get(url string) (r *Response, finalURL string, err os.Error) {
+func (c *Client) Get(url string) (r *Response, err os.Error) {
// TODO: if/when we add cookie support, the redirected request shouldn't
// necessarily supply the same cookies as the original.
var base *URL
via = append(via, &req)
continue
}
- finalURL = url
return
}
ts := httptest.NewServer(robotsTxtHandler)
defer ts.Close()
- r, _, err := Get(ts.URL)
+ r, err := Get(ts.URL)
var b []byte
if err == nil {
b, err = ioutil.ReadAll(r.Body)
defer ts.Close()
c := &Client{}
- _, _, err := c.Get(ts.URL)
+ _, err := c.Get(ts.URL)
if e, g := "Get /?n=10: stopped after 10 redirects", fmt.Sprintf("%v", err); e != g {
t.Errorf("with default client, expected error %q, got %q", e, g)
}
lastVia = via
return checkErr
}}
- _, finalUrl, err := c.Get(ts.URL)
+ res, err := c.Get(ts.URL)
+ finalUrl := res.Request.URL.String()
if e, g := "<nil>", fmt.Sprintf("%v", err); e != g {
t.Errorf("with custom client, expected error %q, got %q", e, g)
}
}
checkErr = os.NewError("no redirects allowed")
- _, finalUrl, err = c.Get(ts.URL)
+ res, err = c.Get(ts.URL)
+ finalUrl = res.Request.URL.String()
if e, g := "Get /?n=1: no redirects allowed", fmt.Sprintf("%v", err); e != g {
t.Errorf("with redirects forbidden, expected error %q, got %q", e, g)
}
}))
defer ts.Close()
get := func(want string) {
- resp, _, err := Get(ts.URL)
+ resp, err := Get(ts.URL)
if err != nil {
t.Fatal(err)
}
// readUsing is the implementation of Read with a replaceable
// ReadResponse-like function, used by the Transport.
-func (cc *ClientConn) readUsing(req *Request, readRes func(buf *bufio.Reader, method string) (*Response, os.Error)) (resp *Response, err os.Error) {
+func (cc *ClientConn) readUsing(req *Request, readRes func(*bufio.Reader, *Request) (*Response, os.Error)) (resp *Response, err os.Error) {
// Retrieve the pipeline ID of this request/response pair
cc.lk.Lock()
id, ok := cc.pipereq[req]
}
}
- resp, err = readRes(r, req.Method)
+ resp, err = readRes(r, req)
cc.lk.Lock()
defer cc.lk.Unlock()
if err != nil {
defer ts.Close()
var end = regexp.MustCompile("/foo/$")
- r, url, err := Get(ts.URL)
+ r, err := Get(ts.URL)
if err != nil {
t.Fatal(err)
}
r.Body.Close()
+ url := r.Request.URL.String()
if r.StatusCode != 200 || !end.MatchString(url) {
t.Fatalf("Get got status %d at %q, want 200 matching /foo/$", r.StatusCode, url)
}
ProtoMajor int // e.g. 1
ProtoMinor int // e.g. 0
- // RequestMethod records the method used in the HTTP request.
- // Header fields such as Content-Length have method-specific meaning.
- RequestMethod string // e.g. "HEAD", "CONNECT", "GET", etc.
-
// Header maps header keys to values. If the response had multiple
// headers with the same key, they will be concatenated, with comma
// delimiters. (Section 4.2 of RFC 2616 requires that multiple headers
// Trailer maps trailer keys to values, in the same
// format as the header.
Trailer Header
+
+ // The Request that was sent to obtain this Response.
+ // Request's Body is nil (having already been consumed).
+ // This is only populated for Client requests.
+ Request *Request
}
-// ReadResponse reads and returns an HTTP response from r. The RequestMethod
-// parameter specifies the method used in the corresponding request (e.g.,
-// "GET", "HEAD"). Clients must call resp.Body.Close when finished reading
-// resp.Body. After that call, clients can inspect resp.Trailer to find
-// key/value pairs included in the response trailer.
-func ReadResponse(r *bufio.Reader, requestMethod string) (resp *Response, err os.Error) {
+// ReadResponse reads and returns an HTTP response from r. The
+// req parameter specifies the Request that corresponds to
+// this Response. Clients must call resp.Body.Close when finished
+// reading resp.Body. After that call, clients can inspect
+// resp.Trailer to find key/value pairs included in the response
+// trailer.
+func ReadResponse(r *bufio.Reader, req *Request) (resp *Response, err os.Error) {
tp := textproto.NewReader(r)
resp = new(Response)
- resp.RequestMethod = strings.ToUpper(requestMethod)
+ resp.Request = req
+ resp.Request.Method = strings.ToUpper(resp.Request.Method)
// Parse the first line of the response.
line, err := tp.ReadLine()
func (resp *Response) Write(w io.Writer) os.Error {
// RequestMethod should be upper-case
- resp.RequestMethod = strings.ToUpper(resp.RequestMethod)
+ if resp.Request != nil {
+ resp.Request.Method = strings.ToUpper(resp.Request.Method)
+ }
// Status line
text := resp.Status
Body string
}
+func dummyReq(method string) *Request {
+ return &Request{Method: method}
+}
+
var respTests = []respTest{
// Unchunked response without Content-Length.
{
"Body here\n",
Response{
- Status: "200 OK",
- StatusCode: 200,
- Proto: "HTTP/1.0",
- ProtoMajor: 1,
- ProtoMinor: 0,
- RequestMethod: "GET",
+ Status: "200 OK",
+ StatusCode: 200,
+ Proto: "HTTP/1.0",
+ ProtoMajor: 1,
+ ProtoMinor: 0,
+ Request: dummyReq("GET"),
Header: Header{
"Connection": {"close"}, // TODO(rsc): Delete?
},
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
- RequestMethod: "GET",
+ Request: dummyReq("GET"),
Close: true,
ContentLength: -1,
},
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
- RequestMethod: "GET",
+ Request: dummyReq("GET"),
Close: false,
ContentLength: 0,
},
"Body here\n",
Response{
- Status: "200 OK",
- StatusCode: 200,
- Proto: "HTTP/1.0",
- ProtoMajor: 1,
- ProtoMinor: 0,
- RequestMethod: "GET",
+ Status: "200 OK",
+ StatusCode: 200,
+ Proto: "HTTP/1.0",
+ ProtoMajor: 1,
+ ProtoMinor: 0,
+ Request: dummyReq("GET"),
Header: Header{
"Connection": {"close"}, // TODO(rsc): Delete?
"Content-Length": {"10"}, // TODO(rsc): Delete?
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
- RequestMethod: "GET",
+ Request: dummyReq("GET"),
Header: Header{},
Close: true,
ContentLength: -1,
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
- RequestMethod: "GET",
+ Request: dummyReq("GET"),
Header: Header{},
Close: true,
ContentLength: -1, // TODO(rsc): Fix?
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
- RequestMethod: "HEAD",
+ Request: dummyReq("HEAD"),
Header: Header{},
Close: true,
ContentLength: 0,
"\r\n",
Response{
- Status: "200 OK",
- StatusCode: 200,
- Proto: "HTTP/1.1",
- ProtoMajor: 1,
- ProtoMinor: 1,
- RequestMethod: "GET",
+ Status: "200 OK",
+ StatusCode: 200,
+ Proto: "HTTP/1.1",
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ Request: dummyReq("GET"),
Header: Header{
"Content-Length": {"0"},
},
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
- RequestMethod: "GET",
+ Request: dummyReq("GET"),
Header: Header{},
Close: true,
ContentLength: -1,
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
- RequestMethod: "GET",
+ Request: dummyReq("GET"),
Header: Header{},
Close: true,
ContentLength: -1,
tt := &respTests[i]
var braw bytes.Buffer
braw.WriteString(tt.Raw)
- resp, err := ReadResponse(bufio.NewReader(&braw), tt.Resp.RequestMethod)
+ resp, err := ReadResponse(bufio.NewReader(&braw), tt.Resp.Request)
if err != nil {
t.Errorf("#%d: %s", i, err)
continue
buf.WriteString("Next Request Here")
bufr := bufio.NewReader(&buf)
- resp, err := ReadResponse(bufr, "GET")
+ resp, err := ReadResponse(bufr, dummyReq("GET"))
checkErr(err, "ReadResponse")
expectedLength := int64(-1)
if !test.chunked {
StatusCode: 503,
ProtoMajor: 1,
ProtoMinor: 0,
- RequestMethod: "GET",
+ Request: dummyReq("GET"),
Header: Header{},
Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")),
ContentLength: 6,
StatusCode: 200,
ProtoMajor: 1,
ProtoMinor: 0,
- RequestMethod: "GET",
+ Request: dummyReq("GET"),
Header: Header{},
Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")),
ContentLength: -1,
StatusCode: 200,
ProtoMajor: 1,
ProtoMinor: 1,
- RequestMethod: "GET",
+ Request: dummyReq("GET"),
Header: Header{},
Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")),
ContentLength: 6,
// Also tests removal of leading and trailing whitespace.
{
Response{
- StatusCode: 204,
- ProtoMajor: 1,
- ProtoMinor: 1,
- RequestMethod: "GET",
+ StatusCode: 204,
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ Request: dummyReq("GET"),
Header: Header{
"Foo": []string{" Bar\nBaz "},
},
frontend := httptest.NewServer(proxyHandler)
defer frontend.Close()
- res, _, err := Get(frontend.URL)
+ res, err := Get(frontend.URL)
if err != nil {
t.Fatalf("Get: %v", err)
}
// Hit the HTTP server successfully.
tr := &Transport{DisableKeepAlives: true} // they interfere with this test
c := &Client{Transport: tr}
- r, _, err := c.Get(url)
+ r, err := c.Get(url)
if err != nil {
t.Fatalf("http Get #1: %v", err)
}
// Hit the HTTP server successfully again, verifying that the
// previous slow connection didn't run our handler. (that we
// get "req=2", not "req=3")
- r, _, err = Get(url)
+ r, err = Get(url)
if err != nil {
t.Fatalf("http Get #2: %v", err)
}
// responses.
for _, te := range []string{"", "identity"} {
url := ts.URL + "/?te=" + te
- res, _, err := Get(url)
+ res, err := Get(url)
if err != nil {
t.Fatalf("error with Get of %s: %v", url, err)
}
// Verify that ErrContentLength is returned
url := ts.URL + "/?overwrite=1"
- _, _, err := Get(url)
+ _, err := Get(url)
if err != nil {
t.Fatalf("error with Get of %s: %v", url, err)
}
}
r := bufio.NewReader(conn)
- _, err = ReadResponse(r, "GET")
+ _, err = ReadResponse(r, &Request{Method: "GET"})
if err != nil {
t.Fatal("ReadResponse error:", err)
}
}))
defer ts.Close()
- res, _, err := Get(ts.URL)
+ res, err := Get(ts.URL)
if err != nil {
t.Fatalf("Get error: %v", err)
}
}))
defer ts.Close()
- res, _, err := Get(ts.URL)
+ res, err := Get(ts.URL)
if err != nil {
t.Fatalf("Get error: %v", err)
}
}
}))
defer ts.Close()
- res, _, err := Get(ts.URL)
+ res, err := Get(ts.URL)
if err != nil {
t.Error(err)
}
if !strings.HasPrefix(ts.URL, "https://") {
t.Fatalf("expected test TLS server to start with https://, got %q", ts.URL)
}
- res, _, err := Get(ts.URL)
+ res, err := Get(ts.URL)
if err != nil {
t.Error(err)
}
// Succeed without timing out:
sendHi <- true
- res, _, err := Get(ts.URL)
+ res, err := Get(ts.URL)
if err != nil {
t.Error(err)
}
// Times out:
timeout <- 1
- res, _, err = Get(ts.URL)
+ res, err = Get(ts.URL)
if err != nil {
t.Error(err)
}
t.TransferEncoding = rr.TransferEncoding
t.Trailer = rr.Trailer
atLeastHTTP11 = rr.ProtoAtLeast(1, 1)
- t.ResponseToHEAD = noBodyExpected(rr.RequestMethod)
+ t.ResponseToHEAD = noBodyExpected(rr.Request.Method)
}
// Sanitize Body,ContentLength,TransferEncoding
case *Response:
t.Header = rr.Header
t.StatusCode = rr.StatusCode
- t.RequestMethod = rr.RequestMethod
+ t.RequestMethod = rr.Request.Method
t.ProtoMajor = rr.ProtoMajor
t.ProtoMinor = rr.ProtoMinor
t.Close = shouldClose(t.ProtoMajor, t.ProtoMinor, t.Header)
}
}
case cm.targetScheme == "https":
- fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\n", cm.targetAddr)
- fmt.Fprintf(conn, "Host: %s\r\n", cm.targetAddr)
+ connectReq := &Request{
+ Method: "CONNECT",
+ RawURL: cm.targetAddr,
+ Host: cm.targetAddr,
+ Header: make(Header),
+ }
if pa != "" {
- fmt.Fprintf(conn, "Proxy-Authorization: %s\r\n", pa)
+ connectReq.Header.Set("Proxy-Authorization", pa)
}
- fmt.Fprintf(conn, "\r\n")
+ connectReq.Write(conn)
// Read response.
// Okay to use and discard buffered reader here, because
// TLS server will not speak until spoken to.
br := bufio.NewReader(conn)
- resp, err := ReadResponse(br, "CONNECT")
+ resp, err := ReadResponse(br, connectReq)
if err != nil {
conn.Close()
return nil, err
}
rc := <-pc.reqch
- resp, err := pc.cc.readUsing(rc.req, func(buf *bufio.Reader, reqMethod string) (*Response, os.Error) {
- resp, err := ReadResponse(buf, reqMethod)
+ resp, err := pc.cc.readUsing(rc.req, func(buf *bufio.Reader, forReq *Request) (*Response, os.Error) {
+ resp, err := ReadResponse(buf, forReq)
if err != nil || resp.ContentLength == 0 {
return resp, err
}
c := &Client{Transport: tr}
fetch := func(n int) string {
- res, _, err := c.Get(ts.URL)
+ res, err := c.Get(ts.URL)
if err != nil {
t.Fatalf("error in disableKeepAlive=%v, req #%d, GET: %v", disableKeepAlive, n, err)
}
t.Errorf("After CloseIdleConnections expected %d idle conn cache keys; got %d", e, g)
}
- resp, _, err := c.Get(ts.URL)
+ resp, err := c.Get(ts.URL)
if err != nil {
t.Error(err)
}
// Their responses will hang until we we write to resch, though.
donech := make(chan bool)
doReq := func() {
- resp, _, err := c.Get(ts.URL)
+ resp, err := c.Get(ts.URL)
if err != nil {
t.Error(err)
}
}
for retries >= 0 {
retries--
- res, _, err := c.Get(ts.URL)
+ res, err := c.Get(ts.URL)
if err != nil {
condFatalf("error in req #%d, GET: %v", n, err)
continue
c := &Client{Transport: &Transport{}}
// First fetch something large, but only read some of it.
- res, _, err := c.Get(ts.URL + "?body=large&chunked=" + chunked)
+ res, err := c.Get(ts.URL + "?body=large&chunked=" + chunked)
if err != nil {
t.Fatalf("large get: %v", err)
}
}
// Then something small.
- res, _, err = c.Get(ts.URL + "?chunked=" + chunked)
+ res, err = c.Get(ts.URL + "?chunked=" + chunked)
if err != nil {
t.Fatal(err)
}
defer ts.Close()
c := &Client{Transport: &Transport{}}
- res, _, err := c.Get(ts.URL)
+ res, err := c.Get(ts.URL)
if err != nil {
t.Fatal(err)
}
// Require successful HTTP response
// before switching to RPC protocol.
- resp, err := http.ReadResponse(bufio.NewReader(conn), "CONNECT")
+ resp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "CONNECT"})
if err == nil && resp.Status == connected {
return NewClient(conn), nil
}
}
// Step 28-29, 32-40. read response from server.
- resp, err := http.ReadResponse(br, "GET")
+ resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
if err != nil {
return err
}
}
bw.WriteString("\r\n")
bw.Flush()
- resp, err := http.ReadResponse(br, "GET")
+ resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
if err != nil {
return
}
// If the client did not send a handshake that matches the protocol
// specification, the server should abort the WebSocket connection.
- _, _, err := http.Get(fmt.Sprintf("http://%s/echo", serverAddr))
+ _, err := http.Get(fmt.Sprintf("http://%s/echo", serverAddr))
if err == nil {
t.Error("Get: unexpected success")
return
func TestHTTPDraft75(t *testing.T) {
once.Do(startServer)
- r, _, err := http.Get(fmt.Sprintf("http://%s/echoDraft75", serverAddr))
+ r, err := http.Get(fmt.Sprintf("http://%s/echoDraft75", serverAddr))
if err != nil {
t.Errorf("Get: error %#v", err)
return