The Go Programming Language

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	}

release.r60.3. Except as noted, this content is licensed under a Creative Commons Attribution 3.0 License.