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