1 |
747 |
jeremybenn |
// Copyright 2009 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 |
|
|
// HTTP client. See RFC 2616.
|
6 |
|
|
//
|
7 |
|
|
// This is the high-level Client interface.
|
8 |
|
|
// The low-level implementation is in transport.go.
|
9 |
|
|
|
10 |
|
|
package http
|
11 |
|
|
|
12 |
|
|
import (
|
13 |
|
|
"encoding/base64"
|
14 |
|
|
"errors"
|
15 |
|
|
"fmt"
|
16 |
|
|
"io"
|
17 |
|
|
"net/url"
|
18 |
|
|
"strings"
|
19 |
|
|
)
|
20 |
|
|
|
21 |
|
|
// A Client is an HTTP client. Its zero value (DefaultClient) is a usable client
|
22 |
|
|
// that uses DefaultTransport.
|
23 |
|
|
//
|
24 |
|
|
// The Client's Transport typically has internal state (cached
|
25 |
|
|
// TCP connections), so Clients should be reused instead of created as
|
26 |
|
|
// needed. Clients are safe for concurrent use by multiple goroutines.
|
27 |
|
|
type Client struct {
|
28 |
|
|
// Transport specifies the mechanism by which individual
|
29 |
|
|
// HTTP requests are made.
|
30 |
|
|
// If nil, DefaultTransport is used.
|
31 |
|
|
Transport RoundTripper
|
32 |
|
|
|
33 |
|
|
// CheckRedirect specifies the policy for handling redirects.
|
34 |
|
|
// If CheckRedirect is not nil, the client calls it before
|
35 |
|
|
// following an HTTP redirect. The arguments req and via
|
36 |
|
|
// are the upcoming request and the requests made already,
|
37 |
|
|
// oldest first. If CheckRedirect returns an error, the client
|
38 |
|
|
// returns that error instead of issue the Request req.
|
39 |
|
|
//
|
40 |
|
|
// If CheckRedirect is nil, the Client uses its default policy,
|
41 |
|
|
// which is to stop after 10 consecutive requests.
|
42 |
|
|
CheckRedirect func(req *Request, via []*Request) error
|
43 |
|
|
|
44 |
|
|
// Jar specifies the cookie jar.
|
45 |
|
|
// If Jar is nil, cookies are not sent in requests and ignored
|
46 |
|
|
// in responses.
|
47 |
|
|
Jar CookieJar
|
48 |
|
|
}
|
49 |
|
|
|
50 |
|
|
// DefaultClient is the default Client and is used by Get, Head, and Post.
|
51 |
|
|
var DefaultClient = &Client{}
|
52 |
|
|
|
53 |
|
|
// RoundTripper is an interface representing the ability to execute a
|
54 |
|
|
// single HTTP transaction, obtaining the Response for a given Request.
|
55 |
|
|
//
|
56 |
|
|
// A RoundTripper must be safe for concurrent use by multiple
|
57 |
|
|
// goroutines.
|
58 |
|
|
type RoundTripper interface {
|
59 |
|
|
// RoundTrip executes a single HTTP transaction, returning
|
60 |
|
|
// the Response for the request req. RoundTrip should not
|
61 |
|
|
// attempt to interpret the response. In particular,
|
62 |
|
|
// RoundTrip must return err == nil if it obtained a response,
|
63 |
|
|
// regardless of the response's HTTP status code. A non-nil
|
64 |
|
|
// err should be reserved for failure to obtain a response.
|
65 |
|
|
// Similarly, RoundTrip should not attempt to handle
|
66 |
|
|
// higher-level protocol details such as redirects,
|
67 |
|
|
// authentication, or cookies.
|
68 |
|
|
//
|
69 |
|
|
// RoundTrip should not modify the request, except for
|
70 |
|
|
// consuming the Body. The request's URL and Header fields
|
71 |
|
|
// are guaranteed to be initialized.
|
72 |
|
|
RoundTrip(*Request) (*Response, error)
|
73 |
|
|
}
|
74 |
|
|
|
75 |
|
|
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
|
76 |
|
|
// return true if the string includes a port.
|
77 |
|
|
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
|
78 |
|
|
|
79 |
|
|
// Used in Send to implement io.ReadCloser by bundling together the
|
80 |
|
|
// bufio.Reader through which we read the response, and the underlying
|
81 |
|
|
// network connection.
|
82 |
|
|
type readClose struct {
|
83 |
|
|
io.Reader
|
84 |
|
|
io.Closer
|
85 |
|
|
}
|
86 |
|
|
|
87 |
|
|
// Do sends an HTTP request and returns an HTTP response, following
|
88 |
|
|
// policy (e.g. redirects, cookies, auth) as configured on the client.
|
89 |
|
|
//
|
90 |
|
|
// A non-nil response always contains a non-nil resp.Body.
|
91 |
|
|
//
|
92 |
|
|
// Callers should close resp.Body when done reading from it. If
|
93 |
|
|
// resp.Body is not closed, the Client's underlying RoundTripper
|
94 |
|
|
// (typically Transport) may not be able to re-use a persistent TCP
|
95 |
|
|
// connection to the server for a subsequent "keep-alive" request.
|
96 |
|
|
//
|
97 |
|
|
// Generally Get, Post, or PostForm will be used instead of Do.
|
98 |
|
|
func (c *Client) Do(req *Request) (resp *Response, err error) {
|
99 |
|
|
if req.Method == "GET" || req.Method == "HEAD" {
|
100 |
|
|
return c.doFollowingRedirects(req)
|
101 |
|
|
}
|
102 |
|
|
return send(req, c.Transport)
|
103 |
|
|
}
|
104 |
|
|
|
105 |
|
|
// send issues an HTTP request. Caller should close resp.Body when done reading from it.
|
106 |
|
|
func send(req *Request, t RoundTripper) (resp *Response, err error) {
|
107 |
|
|
if t == nil {
|
108 |
|
|
t = DefaultTransport
|
109 |
|
|
if t == nil {
|
110 |
|
|
err = errors.New("http: no Client.Transport or DefaultTransport")
|
111 |
|
|
return
|
112 |
|
|
}
|
113 |
|
|
}
|
114 |
|
|
|
115 |
|
|
if req.URL == nil {
|
116 |
|
|
return nil, errors.New("http: nil Request.URL")
|
117 |
|
|
}
|
118 |
|
|
|
119 |
|
|
if req.RequestURI != "" {
|
120 |
|
|
return nil, errors.New("http: Request.RequestURI can't be set in client requests.")
|
121 |
|
|
}
|
122 |
|
|
|
123 |
|
|
// Most the callers of send (Get, Post, et al) don't need
|
124 |
|
|
// Headers, leaving it uninitialized. We guarantee to the
|
125 |
|
|
// Transport that this has been initialized, though.
|
126 |
|
|
if req.Header == nil {
|
127 |
|
|
req.Header = make(Header)
|
128 |
|
|
}
|
129 |
|
|
|
130 |
|
|
if u := req.URL.User; u != nil {
|
131 |
|
|
req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(u.String())))
|
132 |
|
|
}
|
133 |
|
|
return t.RoundTrip(req)
|
134 |
|
|
}
|
135 |
|
|
|
136 |
|
|
// True if the specified HTTP status code is one for which the Get utility should
|
137 |
|
|
// automatically redirect.
|
138 |
|
|
func shouldRedirect(statusCode int) bool {
|
139 |
|
|
switch statusCode {
|
140 |
|
|
case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect:
|
141 |
|
|
return true
|
142 |
|
|
}
|
143 |
|
|
return false
|
144 |
|
|
}
|
145 |
|
|
|
146 |
|
|
// Get issues a GET to the specified URL. If the response is one of the following
|
147 |
|
|
// redirect codes, Get follows the redirect, up to a maximum of 10 redirects:
|
148 |
|
|
//
|
149 |
|
|
// 301 (Moved Permanently)
|
150 |
|
|
// 302 (Found)
|
151 |
|
|
// 303 (See Other)
|
152 |
|
|
// 307 (Temporary Redirect)
|
153 |
|
|
//
|
154 |
|
|
// Caller should close r.Body when done reading from it.
|
155 |
|
|
//
|
156 |
|
|
// Get is a wrapper around DefaultClient.Get.
|
157 |
|
|
func Get(url string) (r *Response, err error) {
|
158 |
|
|
return DefaultClient.Get(url)
|
159 |
|
|
}
|
160 |
|
|
|
161 |
|
|
// Get issues a GET to the specified URL. If the response is one of the
|
162 |
|
|
// following redirect codes, Get follows the redirect after calling the
|
163 |
|
|
// Client's CheckRedirect function.
|
164 |
|
|
//
|
165 |
|
|
// 301 (Moved Permanently)
|
166 |
|
|
// 302 (Found)
|
167 |
|
|
// 303 (See Other)
|
168 |
|
|
// 307 (Temporary Redirect)
|
169 |
|
|
//
|
170 |
|
|
// Caller should close r.Body when done reading from it.
|
171 |
|
|
func (c *Client) Get(url string) (r *Response, err error) {
|
172 |
|
|
req, err := NewRequest("GET", url, nil)
|
173 |
|
|
if err != nil {
|
174 |
|
|
return nil, err
|
175 |
|
|
}
|
176 |
|
|
return c.doFollowingRedirects(req)
|
177 |
|
|
}
|
178 |
|
|
|
179 |
|
|
func (c *Client) doFollowingRedirects(ireq *Request) (r *Response, err error) {
|
180 |
|
|
// TODO: if/when we add cookie support, the redirected request shouldn't
|
181 |
|
|
// necessarily supply the same cookies as the original.
|
182 |
|
|
var base *url.URL
|
183 |
|
|
redirectChecker := c.CheckRedirect
|
184 |
|
|
if redirectChecker == nil {
|
185 |
|
|
redirectChecker = defaultCheckRedirect
|
186 |
|
|
}
|
187 |
|
|
var via []*Request
|
188 |
|
|
|
189 |
|
|
if ireq.URL == nil {
|
190 |
|
|
return nil, errors.New("http: nil Request.URL")
|
191 |
|
|
}
|
192 |
|
|
|
193 |
|
|
jar := c.Jar
|
194 |
|
|
if jar == nil {
|
195 |
|
|
jar = blackHoleJar{}
|
196 |
|
|
}
|
197 |
|
|
|
198 |
|
|
req := ireq
|
199 |
|
|
urlStr := "" // next relative or absolute URL to fetch (after first request)
|
200 |
|
|
for redirect := 0; ; redirect++ {
|
201 |
|
|
if redirect != 0 {
|
202 |
|
|
req = new(Request)
|
203 |
|
|
req.Method = ireq.Method
|
204 |
|
|
req.Header = make(Header)
|
205 |
|
|
req.URL, err = base.Parse(urlStr)
|
206 |
|
|
if err != nil {
|
207 |
|
|
break
|
208 |
|
|
}
|
209 |
|
|
if len(via) > 0 {
|
210 |
|
|
// Add the Referer header.
|
211 |
|
|
lastReq := via[len(via)-1]
|
212 |
|
|
if lastReq.URL.Scheme != "https" {
|
213 |
|
|
req.Header.Set("Referer", lastReq.URL.String())
|
214 |
|
|
}
|
215 |
|
|
|
216 |
|
|
err = redirectChecker(req, via)
|
217 |
|
|
if err != nil {
|
218 |
|
|
break
|
219 |
|
|
}
|
220 |
|
|
}
|
221 |
|
|
}
|
222 |
|
|
|
223 |
|
|
for _, cookie := range jar.Cookies(req.URL) {
|
224 |
|
|
req.AddCookie(cookie)
|
225 |
|
|
}
|
226 |
|
|
urlStr = req.URL.String()
|
227 |
|
|
if r, err = send(req, c.Transport); err != nil {
|
228 |
|
|
break
|
229 |
|
|
}
|
230 |
|
|
if c := r.Cookies(); len(c) > 0 {
|
231 |
|
|
jar.SetCookies(req.URL, c)
|
232 |
|
|
}
|
233 |
|
|
|
234 |
|
|
if shouldRedirect(r.StatusCode) {
|
235 |
|
|
r.Body.Close()
|
236 |
|
|
if urlStr = r.Header.Get("Location"); urlStr == "" {
|
237 |
|
|
err = errors.New(fmt.Sprintf("%d response missing Location header", r.StatusCode))
|
238 |
|
|
break
|
239 |
|
|
}
|
240 |
|
|
base = req.URL
|
241 |
|
|
via = append(via, req)
|
242 |
|
|
continue
|
243 |
|
|
}
|
244 |
|
|
return
|
245 |
|
|
}
|
246 |
|
|
|
247 |
|
|
method := ireq.Method
|
248 |
|
|
err = &url.Error{
|
249 |
|
|
Op: method[0:1] + strings.ToLower(method[1:]),
|
250 |
|
|
URL: urlStr,
|
251 |
|
|
Err: err,
|
252 |
|
|
}
|
253 |
|
|
return
|
254 |
|
|
}
|
255 |
|
|
|
256 |
|
|
func defaultCheckRedirect(req *Request, via []*Request) error {
|
257 |
|
|
if len(via) >= 10 {
|
258 |
|
|
return errors.New("stopped after 10 redirects")
|
259 |
|
|
}
|
260 |
|
|
return nil
|
261 |
|
|
}
|
262 |
|
|
|
263 |
|
|
// Post issues a POST to the specified URL.
|
264 |
|
|
//
|
265 |
|
|
// Caller should close r.Body when done reading from it.
|
266 |
|
|
//
|
267 |
|
|
// Post is a wrapper around DefaultClient.Post
|
268 |
|
|
func Post(url string, bodyType string, body io.Reader) (r *Response, err error) {
|
269 |
|
|
return DefaultClient.Post(url, bodyType, body)
|
270 |
|
|
}
|
271 |
|
|
|
272 |
|
|
// Post issues a POST to the specified URL.
|
273 |
|
|
//
|
274 |
|
|
// Caller should close r.Body when done reading from it.
|
275 |
|
|
func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err error) {
|
276 |
|
|
req, err := NewRequest("POST", url, body)
|
277 |
|
|
if err != nil {
|
278 |
|
|
return nil, err
|
279 |
|
|
}
|
280 |
|
|
req.Header.Set("Content-Type", bodyType)
|
281 |
|
|
r, err = send(req, c.Transport)
|
282 |
|
|
if err == nil && c.Jar != nil {
|
283 |
|
|
c.Jar.SetCookies(req.URL, r.Cookies())
|
284 |
|
|
}
|
285 |
|
|
return r, err
|
286 |
|
|
}
|
287 |
|
|
|
288 |
|
|
// PostForm issues a POST to the specified URL,
|
289 |
|
|
// with data's keys and values urlencoded as the request body.
|
290 |
|
|
//
|
291 |
|
|
// Caller should close r.Body when done reading from it.
|
292 |
|
|
//
|
293 |
|
|
// PostForm is a wrapper around DefaultClient.PostForm
|
294 |
|
|
func PostForm(url string, data url.Values) (r *Response, err error) {
|
295 |
|
|
return DefaultClient.PostForm(url, data)
|
296 |
|
|
}
|
297 |
|
|
|
298 |
|
|
// PostForm issues a POST to the specified URL,
|
299 |
|
|
// with data's keys and values urlencoded as the request body.
|
300 |
|
|
//
|
301 |
|
|
// Caller should close r.Body when done reading from it.
|
302 |
|
|
func (c *Client) PostForm(url string, data url.Values) (r *Response, err error) {
|
303 |
|
|
return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
304 |
|
|
}
|
305 |
|
|
|
306 |
|
|
// Head issues a HEAD to the specified URL. If the response is one of the
|
307 |
|
|
// following redirect codes, Head follows the redirect after calling the
|
308 |
|
|
// Client's CheckRedirect function.
|
309 |
|
|
//
|
310 |
|
|
// 301 (Moved Permanently)
|
311 |
|
|
// 302 (Found)
|
312 |
|
|
// 303 (See Other)
|
313 |
|
|
// 307 (Temporary Redirect)
|
314 |
|
|
//
|
315 |
|
|
// Head is a wrapper around DefaultClient.Head
|
316 |
|
|
func Head(url string) (r *Response, err error) {
|
317 |
|
|
return DefaultClient.Head(url)
|
318 |
|
|
}
|
319 |
|
|
|
320 |
|
|
// Head issues a HEAD to the specified URL. If the response is one of the
|
321 |
|
|
// following redirect codes, Head follows the redirect after calling the
|
322 |
|
|
// Client's CheckRedirect function.
|
323 |
|
|
//
|
324 |
|
|
// 301 (Moved Permanently)
|
325 |
|
|
// 302 (Found)
|
326 |
|
|
// 303 (See Other)
|
327 |
|
|
// 307 (Temporary Redirect)
|
328 |
|
|
func (c *Client) Head(url string) (r *Response, err error) {
|
329 |
|
|
req, err := NewRequest("HEAD", url, nil)
|
330 |
|
|
if err != nil {
|
331 |
|
|
return nil, err
|
332 |
|
|
}
|
333 |
|
|
return c.doFollowingRedirects(req)
|
334 |
|
|
}
|