| // 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); | |
| } |