PageRenderTime 62ms CodeModel.GetById 16ms app.highlight 40ms RepoModel.GetById 2ms app.codeStats 0ms

/http/client.go

http://github.com/petar/GoHTTP
Go | 295 lines | 147 code | 28 blank | 120 comment | 38 complexity | 3c80cbd24656d69bf86582d2113076b3 MD5 | raw file
  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
  7package http
  8
  9import (
 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.
 26type 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.
 41var 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.
 48type 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.
 66func 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.
 71type 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// A non-nil response always contains a non-nil resp.Body.
 80//
 81// Callers should close resp.Body when done reading from it. If
 82// resp.Body is not closed, the Client's underlying RoundTripper
 83// (typically Transport) may not be able to re-use a persistent TCP
 84// connection to the server for a subsequent "keep-alive" request.
 85//
 86// Generally Get, Post, or PostForm will be used instead of Do.
 87func (c *Client) Do(req *Request) (resp *Response, err os.Error) {
 88	if req.Method == "GET" || req.Method == "HEAD" {
 89		return c.doFollowingRedirects(req)
 90	}
 91	return send(req, c.Transport)
 92}
 93
 94// send issues an HTTP request.  Caller should close resp.Body when done reading from it.
 95func send(req *Request, t RoundTripper) (resp *Response, err os.Error) {
 96	if t == nil {
 97		t = DefaultTransport
 98		if t == nil {
 99			err = os.NewError("no http.Client.Transport or http.DefaultTransport")
100			return
101		}
102	}
103
104	// Most the callers of send (Get, Post, et al) don't need
105	// Headers, leaving it uninitialized.  We guarantee to the
106	// Transport that this has been initialized, though.
107	if req.Header == nil {
108		req.Header = make(Header)
109	}
110
111	info := req.URL.RawUserinfo
112	if len(info) > 0 {
113		if req.Header == nil {
114			req.Header = make(Header)
115		}
116		req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(info)))
117	}
118	return t.RoundTrip(req)
119}
120
121// True if the specified HTTP status code is one for which the Get utility should
122// automatically redirect.
123func shouldRedirect(statusCode int) bool {
124	switch statusCode {
125	case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect:
126		return true
127	}
128	return false
129}
130
131// Get issues a GET to the specified URL.  If the response is one of the following
132// redirect codes, Get follows the redirect, up to a maximum of 10 redirects:
133//
134//    301 (Moved Permanently)
135//    302 (Found)
136//    303 (See Other)
137//    307 (Temporary Redirect)
138//
139// Caller should close r.Body when done reading from it.
140//
141// Get is a convenience wrapper around DefaultClient.Get.
142func Get(url string) (r *Response, err os.Error) {
143	return DefaultClient.Get(url)
144}
145
146// Get issues a GET to the specified URL.  If the response is one of the
147// following redirect codes, Get follows the redirect after calling the
148// Client's CheckRedirect function.
149//
150//    301 (Moved Permanently)
151//    302 (Found)
152//    303 (See Other)
153//    307 (Temporary Redirect)
154//
155// Caller should close r.Body when done reading from it.
156func (c *Client) Get(url string) (r *Response, err os.Error) {
157	req, err := NewRequest("GET", url, nil)
158	if err != nil {
159		return nil, err
160	}
161	return c.doFollowingRedirects(req)
162}
163
164func (c *Client) doFollowingRedirects(ireq *Request) (r *Response, err os.Error) {
165	// TODO: if/when we add cookie support, the redirected request shouldn't
166	// necessarily supply the same cookies as the original.
167	var base *url.URL
168	redirectChecker := c.CheckRedirect
169	if redirectChecker == nil {
170		redirectChecker = defaultCheckRedirect
171	}
172	var via []*Request
173
174	req := ireq
175	urlStr := "" // next relative or absolute URL to fetch (after first request)
176	for redirect := 0; ; redirect++ {
177		if redirect != 0 {
178			req = new(Request)
179			req.Method = ireq.Method
180			req.Header = make(Header)
181			req.URL, err = base.Parse(urlStr)
182			if err != nil {
183				break
184			}
185			if len(via) > 0 {
186				// Add the Referer header.
187				lastReq := via[len(via)-1]
188				if lastReq.URL.Scheme != "https" {
189					req.Header.Set("Referer", lastReq.URL.String())
190				}
191
192				err = redirectChecker(req, via)
193				if err != nil {
194					break
195				}
196			}
197		}
198
199		urlStr = req.URL.String()
200		if r, err = send(req, c.Transport); err != nil {
201			break
202		}
203		if shouldRedirect(r.StatusCode) {
204			r.Body.Close()
205			if urlStr = r.Header.Get("Location"); urlStr == "" {
206				err = os.NewError(fmt.Sprintf("%d response missing Location header", r.StatusCode))
207				break
208			}
209			base = req.URL
210			via = append(via, req)
211			continue
212		}
213		return
214	}
215
216	method := ireq.Method
217	err = &url.Error{method[0:1] + strings.ToLower(method[1:]), urlStr, err}
218	return
219}
220
221func defaultCheckRedirect(req *Request, via []*Request) os.Error {
222	if len(via) >= 10 {
223		return os.NewError("stopped after 10 redirects")
224	}
225	return nil
226}
227
228// Post issues a POST to the specified URL.
229//
230// Caller should close r.Body when done reading from it.
231//
232// Post is a wrapper around DefaultClient.Post
233func Post(url string, bodyType string, body io.Reader) (r *Response, err os.Error) {
234	return DefaultClient.Post(url, bodyType, body)
235}
236
237// Post issues a POST to the specified URL.
238//
239// Caller should close r.Body when done reading from it.
240func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err os.Error) {
241	req, err := NewRequest("POST", url, body)
242	if err != nil {
243		return nil, err
244	}
245	req.Header.Set("Content-Type", bodyType)
246	return send(req, c.Transport)
247}
248
249// PostForm issues a POST to the specified URL, 
250// with data's keys and values urlencoded as the request body.
251//
252// Caller should close r.Body when done reading from it.
253//
254// PostForm is a wrapper around DefaultClient.PostForm
255func PostForm(url string, data url.Values) (r *Response, err os.Error) {
256	return DefaultClient.PostForm(url, data)
257}
258
259// PostForm issues a POST to the specified URL, 
260// with data's keys and values urlencoded as the request body.
261//
262// Caller should close r.Body when done reading from it.
263func (c *Client) PostForm(url string, data url.Values) (r *Response, err os.Error) {
264	return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
265}
266
267// Head issues a HEAD to the specified URL.  If the response is one of the
268// following redirect codes, Head follows the redirect after calling the
269// Client's CheckRedirect function.
270//
271//    301 (Moved Permanently)
272//    302 (Found)
273//    303 (See Other)
274//    307 (Temporary Redirect)
275//
276// Head is a wrapper around DefaultClient.Head
277func Head(url string) (r *Response, err os.Error) {
278	return DefaultClient.Head(url)
279}
280
281// Head issues a HEAD to the specified URL.  If the response is one of the
282// following redirect codes, Head follows the redirect after calling the
283// Client's CheckRedirect function.
284//
285//    301 (Moved Permanently)
286//    302 (Found)
287//    303 (See Other)
288//    307 (Temporary Redirect)
289func (c *Client) Head(url string) (r *Response, err os.Error) {
290	req, err := NewRequest("HEAD", url, nil)
291	if err != nil {
292		return nil, err
293	}
294	return c.doFollowingRedirects(req)
295}