| // Copyright 2009 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. |
| |
| // Primitive HTTP client. See RFC 2616. |
| |
| package http |
| |
| import ( |
| "bufio"; |
| "fmt"; |
| "http"; |
| "io"; |
| "log"; |
| "net"; |
| "os"; |
| "strconv"; |
| "strings"; |
| ) |
| |
| // Response represents the response from an HTTP request. |
| type Response struct { |
| Status string; // e.g. "200 OK" |
| StatusCode int; // e.g. 200 |
| |
| // 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 |
| // be semantically equivalent to a comma-delimited sequence.) |
| // |
| // Keys in the map are canonicalized (see CanonicalHeaderKey). |
| Header map [string] string; |
| |
| // Stream from which the response body can be read. |
| Body io.ReadCloser; |
| } |
| |
| // GetHeader returns the value of the response header with the given |
| // key, and true. If there were multiple headers with this key, their |
| // values are concatenated, with a comma delimiter. If there were no |
| // response headers with the given key, it returns the empty string and |
| // false. Keys are not case sensitive. |
| func (r *Response) GetHeader(key string) (value string) { |
| value, present := r.Header[CanonicalHeaderKey(key)]; |
| return; |
| } |
| |
| // AddHeader adds a value under the given key. Keys are not case sensitive. |
| func (r *Response) AddHeader(key, value string) { |
| key = CanonicalHeaderKey(key); |
| |
| oldValues, oldValuesPresent := r.Header[key]; |
| if oldValuesPresent { |
| r.Header[key] = oldValues + "," + value; |
| } else { |
| r.Header[key] = value; |
| } |
| } |
| |
| // Given a string of the form "host", "host:port", or "[ipv6::address]:port", |
| // return true if the string includes a port. |
| func hasPort(s string) bool { |
| return strings.LastIndex(s, ":") > strings.LastIndex(s, "]"); |
| } |
| |
| // Used in Send to implement io.ReadCloser by bundling together the |
| // io.BufReader through which we read the response, and the underlying |
| // network connection. |
| type readClose struct { |
| io.Reader; |
| io.Closer; |
| } |
| |
| // Send issues an HTTP request. Caller should close resp.Body when done reading it. |
| // |
| // TODO: support persistent connections (multiple requests on a single connection). |
| // send() method is nonpublic because, when we refactor the code for persistent |
| // connections, it may no longer make sense to have a method with this signature. |
| func send(req *Request) (resp *Response, err os.Error) { |
| if req.Url.Scheme != "http" { |
| return nil, &badStringError{"unsupported protocol scheme", req.Url.Scheme}; |
| } |
| |
| addr := req.Url.Host; |
| if !hasPort(addr) { |
| addr += ":http"; |
| } |
| conn, err := net.Dial("tcp", "", addr); |
| if err != nil { |
| return nil, err; |
| } |
| |
| // Close the connection if we encounter an error during header parsing. We'll |
| // cancel this when we hand the connection off to our caller. |
| defer func() { if conn != nil { conn.Close() } }(); |
| |
| err = req.write(conn); |
| if err != nil { |
| return nil, err; |
| } |
| |
| // Parse the first line of the response. |
| resp = new(Response); |
| resp.Header = make(map[string] string); |
| reader := bufio.NewReader(conn); |
| |
| line, err := readLine(reader); |
| if err != nil { |
| return nil, err; |
| } |
| f := strings.Split(line, " ", 3); |
| if len(f) < 3 { |
| return nil, &badStringError{"malformed HTTP response", line}; |
| } |
| resp.Status = f[1] + " " + f[2]; |
| resp.StatusCode, err = strconv.Atoi(f[1]); |
| if err != nil { |
| return nil, &badStringError{"malformed HTTP status code", f[1]}; |
| } |
| |
| // Parse the response headers. |
| for { |
| key, value, err := readKeyValue(reader); |
| if err != nil { |
| return nil, err; |
| } |
| if key == "" { |
| break; // end of response header |
| } |
| resp.AddHeader(key, value); |
| } |
| |
| // TODO(rsc): Make this work: |
| // r := io.Reader(reader); |
| var r io.Reader = reader; |
| if v := resp.GetHeader("Transfer-Encoding"); v == "chunked" { |
| r = newChunkedReader(reader); |
| } |
| resp.Body = readClose{ r, conn }; |
| |
| conn = nil; // so that defered func won't close it |
| err = nil; |
| return; |
| } |
| |
| // True if the specified HTTP status code is one for which the Get utility should |
| // automatically redirect. |
| func shouldRedirect(statusCode int) bool { |
| switch statusCode { |
| case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect: |
| return true; |
| } |
| return false; |
| } |
| |
| // Get issues a GET to the specified URL. If the response is one of the following |
| // redirect codes, it follows the redirect, up to a maximum of 10 redirects: |
| // |
| // 301 (Moved Permanently) |
| // 302 (Found) |
| // 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 it. |
| func Get(url string) (r *Response, finalURL string, err os.Error) { |
| // TODO: if/when we add cookie support, the redirected request shouldn't |
| // necessarily supply the same cookies as the original. |
| // TODO: set referrer header on redirects. |
| for redirect := 0;; redirect++ { |
| if redirect >= 10 { |
| err = os.ErrorString("stopped after 10 redirects"); |
| break; |
| } |
| |
| var req Request; |
| if req.Url, err = ParseURL(url); err != nil { |
| break; |
| } |
| if r, err = send(&req); err != nil { |
| break; |
| } |
| if shouldRedirect(r.StatusCode) { |
| r.Body.Close(); |
| if url = r.GetHeader("Location"); url == "" { |
| err = os.ErrorString(fmt.Sprintf("%d response missing Location header", r.StatusCode)); |
| break; |
| } |
| } |
| finalURL = url; |
| return; |
| } |
| |
| err = &URLError{"Get", url, err}; |
| return; |
| } |
| |
| |
| // Post issues a POST to the specified URL. |
| // |
| // Caller should close r.Body when done reading it. |
| func Post(url string, bodyType string, body io.Reader) (r *Response, err os.Error) { |
| var req Request; |
| req.Method = "POST"; |
| req.Body = body; |
| req.Header = map[string] string{ |
| "Content-Type": bodyType, |
| "Transfer-Encoding": "chunked", |
| }; |
| |
| req.Url, err = ParseURL(url); |
| if err != nil { |
| return nil, err; |
| } |
| |
| return send(&req); |
| } |