The Go Programming Language

Source file src/pkg/http/response.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	// HTTP Response reading and parsing.
     6	
     7	package http
     8	
     9	import (
    10		"bufio"
    11		"io"
    12		"net/textproto"
    13		"os"
    14		"strconv"
    15		"strings"
    16	)
    17	
    18	var respExcludeHeader = map[string]bool{
    19		"Content-Length":    true,
    20		"Transfer-Encoding": true,
    21		"Trailer":           true,
    22	}
    23	
    24	// Response represents the response from an HTTP request.
    25	//
    26	type Response struct {
    27		Status     string // e.g. "200 OK"
    28		StatusCode int    // e.g. 200
    29		Proto      string // e.g. "HTTP/1.0"
    30		ProtoMajor int    // e.g. 1
    31		ProtoMinor int    // e.g. 0
    32	
    33		// Header maps header keys to values.  If the response had multiple
    34		// headers with the same key, they will be concatenated, with comma
    35		// delimiters.  (Section 4.2 of RFC 2616 requires that multiple headers
    36		// be semantically equivalent to a comma-delimited sequence.) Values
    37		// duplicated by other fields in this struct (e.g., ContentLength) are
    38		// omitted from Header.
    39		//
    40		// Keys in the map are canonicalized (see CanonicalHeaderKey).
    41		Header Header
    42	
    43		// Body represents the response body.
    44		Body io.ReadCloser
    45	
    46		// ContentLength records the length of the associated content.  The
    47		// value -1 indicates that the length is unknown.  Unless RequestMethod
    48		// is "HEAD", values >= 0 indicate that the given number of bytes may
    49		// be read from Body.
    50		ContentLength int64
    51	
    52		// Contains transfer encodings from outer-most to inner-most. Value is
    53		// nil, means that "identity" encoding is used.
    54		TransferEncoding []string
    55	
    56		// Close records whether the header directed that the connection be
    57		// closed after reading Body.  The value is advice for clients: neither
    58		// ReadResponse nor Response.Write ever closes a connection.
    59		Close bool
    60	
    61		// Trailer maps trailer keys to values, in the same
    62		// format as the header.
    63		Trailer Header
    64	
    65		// The Request that was sent to obtain this Response.
    66		// Request's Body is nil (having already been consumed).
    67		// This is only populated for Client requests.
    68		Request *Request
    69	}
    70	
    71	// Cookies parses and returns the cookies set in the Set-Cookie headers.
    72	func (r *Response) Cookies() []*Cookie {
    73		return readSetCookies(r.Header)
    74	}
    75	
    76	// ReadResponse reads and returns an HTTP response from r.  The
    77	// req parameter specifies the Request that corresponds to
    78	// this Response.  Clients must call resp.Body.Close when finished
    79	// reading resp.Body.  After that call, clients can inspect
    80	// resp.Trailer to find key/value pairs included in the response
    81	// trailer.
    82	func ReadResponse(r *bufio.Reader, req *Request) (resp *Response, err os.Error) {
    83	
    84		tp := textproto.NewReader(r)
    85		resp = new(Response)
    86	
    87		resp.Request = req
    88		resp.Request.Method = strings.ToUpper(resp.Request.Method)
    89	
    90		// Parse the first line of the response.
    91		line, err := tp.ReadLine()
    92		if err != nil {
    93			if err == os.EOF {
    94				err = io.ErrUnexpectedEOF
    95			}
    96			return nil, err
    97		}
    98		f := strings.SplitN(line, " ", 3)
    99		if len(f) < 2 {
   100			return nil, &badStringError{"malformed HTTP response", line}
   101		}
   102		reasonPhrase := ""
   103		if len(f) > 2 {
   104			reasonPhrase = f[2]
   105		}
   106		resp.Status = f[1] + " " + reasonPhrase
   107		resp.StatusCode, err = strconv.Atoi(f[1])
   108		if err != nil {
   109			return nil, &badStringError{"malformed HTTP status code", f[1]}
   110		}
   111	
   112		resp.Proto = f[0]
   113		var ok bool
   114		if resp.ProtoMajor, resp.ProtoMinor, ok = ParseHTTPVersion(resp.Proto); !ok {
   115			return nil, &badStringError{"malformed HTTP version", resp.Proto}
   116		}
   117	
   118		// Parse the response headers.
   119		mimeHeader, err := tp.ReadMIMEHeader()
   120		if err != nil {
   121			return nil, err
   122		}
   123		resp.Header = Header(mimeHeader)
   124	
   125		fixPragmaCacheControl(resp.Header)
   126	
   127		err = readTransfer(resp, r)
   128		if err != nil {
   129			return nil, err
   130		}
   131	
   132		return resp, nil
   133	}
   134	
   135	// RFC2616: Should treat
   136	//	Pragma: no-cache
   137	// like
   138	//	Cache-Control: no-cache
   139	func fixPragmaCacheControl(header Header) {
   140		if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" {
   141			if _, presentcc := header["Cache-Control"]; !presentcc {
   142				header["Cache-Control"] = []string{"no-cache"}
   143			}
   144		}
   145	}
   146	
   147	// ProtoAtLeast returns whether the HTTP protocol used
   148	// in the response is at least major.minor.
   149	func (r *Response) ProtoAtLeast(major, minor int) bool {
   150		return r.ProtoMajor > major ||
   151			r.ProtoMajor == major && r.ProtoMinor >= minor
   152	}
   153	
   154	// Writes the response (header, body and trailer) in wire format. This method
   155	// consults the following fields of resp:
   156	//
   157	//  StatusCode
   158	//  ProtoMajor
   159	//  ProtoMinor
   160	//  RequestMethod
   161	//  TransferEncoding
   162	//  Trailer
   163	//  Body
   164	//  ContentLength
   165	//  Header, values for non-canonical keys will have unpredictable behavior
   166	//
   167	func (resp *Response) Write(w io.Writer) os.Error {
   168	
   169		// RequestMethod should be upper-case
   170		if resp.Request != nil {
   171			resp.Request.Method = strings.ToUpper(resp.Request.Method)
   172		}
   173	
   174		// Status line
   175		text := resp.Status
   176		if text == "" {
   177			var ok bool
   178			text, ok = statusText[resp.StatusCode]
   179			if !ok {
   180				text = "status code " + strconv.Itoa(resp.StatusCode)
   181			}
   182		}
   183		io.WriteString(w, "HTTP/"+strconv.Itoa(resp.ProtoMajor)+".")
   184		io.WriteString(w, strconv.Itoa(resp.ProtoMinor)+" ")
   185		io.WriteString(w, strconv.Itoa(resp.StatusCode)+" "+text+"\r\n")
   186	
   187		// Process Body,ContentLength,Close,Trailer
   188		tw, err := newTransferWriter(resp)
   189		if err != nil {
   190			return err
   191		}
   192		err = tw.WriteHeader(w)
   193		if err != nil {
   194			return err
   195		}
   196	
   197		// Rest of header
   198		err = resp.Header.WriteSubset(w, respExcludeHeader)
   199		if err != nil {
   200			return err
   201		}
   202	
   203		// End-of-header
   204		io.WriteString(w, "\r\n")
   205	
   206		// Write body and trailer
   207		err = tw.WriteBody(w)
   208		if err != nil {
   209			return err
   210		}
   211	
   212		// Success
   213		return nil
   214	}

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