123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- // 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.
- // HTTP Response reading and parsing.
- package http
- import (
- "bufio"
- "bytes"
- "crypto/tls"
- "errors"
- "fmt"
- "io"
- "net/textproto"
- "net/url"
- "strconv"
- "strings"
- "golang.org/x/net/http/httpguts"
- )
- var respExcludeHeader = map[string]bool{
- "Content-Length": true,
- "Transfer-Encoding": true,
- "Trailer": true,
- }
- // Response represents the response from an HTTP request.
- //
- // The Client and Transport return Responses from servers once
- // the response headers have been received. The response body
- // is streamed on demand as the Body field is read.
- type Response struct {
- Status string // e.g. "200 OK"
- StatusCode int // e.g. 200
- Proto string // e.g. "HTTP/1.0"
- ProtoMajor int // e.g. 1
- ProtoMinor int // e.g. 0
- // Header maps header keys to values. If the response had multiple
- // headers with the same key, they may be concatenated, with comma
- // delimiters. (RFC 7230, section 3.2.2 requires that multiple headers
- // be semantically equivalent to a comma-delimited sequence.) When
- // Header values are duplicated by other fields in this struct (e.g.,
- // ContentLength, TransferEncoding, Trailer), the field values are
- // authoritative.
- //
- // Keys in the map are canonicalized (see CanonicalHeaderKey).
- Header Header
- // Body represents the response body.
- //
- // The response body is streamed on demand as the Body field
- // is read. If the network connection fails or the server
- // terminates the response, Body.Read calls return an error.
- //
- // The http Client and Transport guarantee that Body is always
- // non-nil, even on responses without a body or responses with
- // a zero-length body. It is the caller's responsibility to
- // close Body. The default HTTP client's Transport may not
- // reuse HTTP/1.x "keep-alive" TCP connections if the Body is
- // not read to completion and closed.
- //
- // The Body is automatically dechunked if the server replied
- // with a "chunked" Transfer-Encoding.
- //
- // As of Go 1.12, the Body will also implement io.Writer
- // on a successful "101 Switching Protocols" response,
- // as used by WebSockets and HTTP/2's "h2c" mode.
- Body io.ReadCloser
- // ContentLength records the length of the associated content. The
- // value -1 indicates that the length is unknown. Unless Request.Method
- // is "HEAD", values >= 0 indicate that the given number of bytes may
- // be read from Body.
- ContentLength int64
- // Contains transfer encodings from outer-most to inner-most. Value is
- // nil, means that "identity" encoding is used.
- TransferEncoding []string
- // Close records whether the header directed that the connection be
- // closed after reading Body. The value is advice for clients: neither
- // ReadResponse nor Response.Write ever closes a connection.
- Close bool
- // Uncompressed reports whether the response was sent compressed but
- // was decompressed by the http package. When true, reading from
- // Body yields the uncompressed content instead of the compressed
- // content actually set from the server, ContentLength is set to -1,
- // and the "Content-Length" and "Content-Encoding" fields are deleted
- // from the responseHeader. To get the original response from
- // the server, set Transport.DisableCompression to true.
- Uncompressed bool
- // Trailer maps trailer keys to values in the same
- // format as Header.
- //
- // The Trailer initially contains only nil values, one for
- // each key specified in the server's "Trailer" header
- // value. Those values are not added to Header.
- //
- // Trailer must not be accessed concurrently with Read calls
- // on the Body.
- //
- // After Body.Read has returned io.EOF, Trailer will contain
- // any trailer values sent by the server.
- Trailer Header
- // Request is the request that was sent to obtain this Response.
- // Request's Body is nil (having already been consumed).
- // This is only populated for Client requests.
- Request *Request
- // TLS contains information about the TLS connection on which the
- // response was received. It is nil for unencrypted responses.
- // The pointer is shared between responses and should not be
- // modified.
- TLS *tls.ConnectionState
- }
- // Cookies parses and returns the cookies set in the Set-Cookie headers.
- func (r *Response) Cookies() []*Cookie {
- return readSetCookies(r.Header)
- }
- // ErrNoLocation is returned by Response's Location method
- // when no Location header is present.
- var ErrNoLocation = errors.New("http: no Location header in response")
- // Location returns the URL of the response's "Location" header,
- // if present. Relative redirects are resolved relative to
- // the Response's Request. ErrNoLocation is returned if no
- // Location header is present.
- func (r *Response) Location() (*url.URL, error) {
- lv := r.Header.Get("Location")
- if lv == "" {
- return nil, ErrNoLocation
- }
- if r.Request != nil && r.Request.URL != nil {
- return r.Request.URL.Parse(lv)
- }
- return url.Parse(lv)
- }
- // ReadResponse reads and returns an HTTP response from r.
- // The req parameter optionally specifies the Request that corresponds
- // to this Response. If nil, a GET request is assumed.
- // Clients must call resp.Body.Close when finished reading resp.Body.
- // After that call, clients can inspect resp.Trailer to find key/value
- // pairs included in the response trailer.
- func ReadResponse(r *bufio.Reader, req *Request) (*Response, error) {
- tp := textproto.NewReader(r)
- resp := &Response{
- Request: req,
- }
- // Parse the first line of the response.
- line, err := tp.ReadLine()
- if err != nil {
- if err == io.EOF {
- err = io.ErrUnexpectedEOF
- }
- return nil, err
- }
- proto, status, ok := strings.Cut(line, " ")
- if !ok {
- return nil, badStringError("malformed HTTP response", line)
- }
- resp.Proto = proto
- resp.Status = strings.TrimLeft(status, " ")
- statusCode, _, _ := strings.Cut(resp.Status, " ")
- if len(statusCode) != 3 {
- return nil, badStringError("malformed HTTP status code", statusCode)
- }
- resp.StatusCode, err = strconv.Atoi(statusCode)
- if err != nil || resp.StatusCode < 0 {
- return nil, badStringError("malformed HTTP status code", statusCode)
- }
- if resp.ProtoMajor, resp.ProtoMinor, ok = ParseHTTPVersion(resp.Proto); !ok {
- return nil, badStringError("malformed HTTP version", resp.Proto)
- }
- // Parse the response headers.
- mimeHeader, err := tp.ReadMIMEHeader()
- if err != nil {
- if err == io.EOF {
- err = io.ErrUnexpectedEOF
- }
- return nil, err
- }
- resp.Header = Header(mimeHeader)
- fixPragmaCacheControl(resp.Header)
- err = readTransfer(resp, r)
- if err != nil {
- return nil, err
- }
- return resp, nil
- }
- // RFC 7234, section 5.4: Should treat
- // Pragma: no-cache
- // like
- // Cache-Control: no-cache
- func fixPragmaCacheControl(header Header) {
- if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" {
- if _, presentcc := header["Cache-Control"]; !presentcc {
- header["Cache-Control"] = []string{"no-cache"}
- }
- }
- }
- // ProtoAtLeast reports whether the HTTP protocol used
- // in the response is at least major.minor.
- func (r *Response) ProtoAtLeast(major, minor int) bool {
- return r.ProtoMajor > major ||
- r.ProtoMajor == major && r.ProtoMinor >= minor
- }
- // Write writes r to w in the HTTP/1.x server response format,
- // including the status line, headers, body, and optional trailer.
- //
- // This method consults the following fields of the response r:
- //
- // StatusCode
- // ProtoMajor
- // ProtoMinor
- // Request.Method
- // TransferEncoding
- // Trailer
- // Body
- // ContentLength
- // Header, values for non-canonical keys will have unpredictable behavior
- //
- // The Response Body is closed after it is sent.
- func (r *Response) Write(w io.Writer) error {
- // Status line
- text := r.Status
- if text == "" {
- var ok bool
- text, ok = statusText[r.StatusCode]
- if !ok {
- text = "status code " + strconv.Itoa(r.StatusCode)
- }
- } else {
- // Just to reduce stutter, if user set r.Status to "200 OK" and StatusCode to 200.
- // Not important.
- text = strings.TrimPrefix(text, strconv.Itoa(r.StatusCode)+" ")
- }
- if _, err := fmt.Fprintf(w, "HTTP/%d.%d %03d %s\r\n", r.ProtoMajor, r.ProtoMinor, r.StatusCode, text); err != nil {
- return err
- }
- // Clone it, so we can modify r1 as needed.
- r1 := new(Response)
- *r1 = *r
- if r1.ContentLength == 0 && r1.Body != nil {
- // Is it actually 0 length? Or just unknown?
- var buf [1]byte
- n, err := r1.Body.Read(buf[:])
- if err != nil && err != io.EOF {
- return err
- }
- if n == 0 {
- // Reset it to a known zero reader, in case underlying one
- // is unhappy being read repeatedly.
- r1.Body = NoBody
- } else {
- r1.ContentLength = -1
- r1.Body = struct {
- io.Reader
- io.Closer
- }{
- io.MultiReader(bytes.NewReader(buf[:1]), r.Body),
- r.Body,
- }
- }
- }
- // If we're sending a non-chunked HTTP/1.1 response without a
- // content-length, the only way to do that is the old HTTP/1.0
- // way, by noting the EOF with a connection close, so we need
- // to set Close.
- if r1.ContentLength == -1 && !r1.Close && r1.ProtoAtLeast(1, 1) && !chunked(r1.TransferEncoding) && !r1.Uncompressed {
- r1.Close = true
- }
- // Process Body,ContentLength,Close,Trailer
- tw, err := newTransferWriter(r1)
- if err != nil {
- return err
- }
- err = tw.writeHeader(w, nil)
- if err != nil {
- return err
- }
- // Rest of header
- err = r.Header.WriteSubset(w, respExcludeHeader)
- if err != nil {
- return err
- }
- // contentLengthAlreadySent may have been already sent for
- // POST/PUT requests, even if zero length. See Issue 8180.
- contentLengthAlreadySent := tw.shouldSendContentLength()
- if r1.ContentLength == 0 && !chunked(r1.TransferEncoding) && !contentLengthAlreadySent && bodyAllowedForStatus(r.StatusCode) {
- if _, err := io.WriteString(w, "Content-Length: 0\r\n"); err != nil {
- return err
- }
- }
- // End-of-header
- if _, err := io.WriteString(w, "\r\n"); err != nil {
- return err
- }
- // Write body and trailer
- err = tw.writeBody(w)
- if err != nil {
- return err
- }
- // Success
- return nil
- }
- func (r *Response) closeBody() {
- if r.Body != nil {
- r.Body.Close()
- }
- }
- // bodyIsWritable reports whether the Body supports writing. The
- // Transport returns Writable bodies for 101 Switching Protocols
- // responses.
- // The Transport uses this method to determine whether a persistent
- // connection is done being managed from its perspective. Once we
- // return a writable response body to a user, the net/http package is
- // done managing that connection.
- func (r *Response) bodyIsWritable() bool {
- _, ok := r.Body.(io.Writer)
- return ok
- }
- // isProtocolSwitch reports whether the response code and header
- // indicate a successful protocol upgrade response.
- func (r *Response) isProtocolSwitch() bool {
- return isProtocolSwitchResponse(r.StatusCode, r.Header)
- }
- // isProtocolSwitchResponse reports whether the response code and
- // response header indicate a successful protocol upgrade response.
- func isProtocolSwitchResponse(code int, h Header) bool {
- return code == StatusSwitchingProtocols && isProtocolSwitchHeader(h)
- }
- // isProtocolSwitchHeader reports whether the request or response header
- // is for a protocol switch.
- func isProtocolSwitchHeader(h Header) bool {
- return h.Get("Upgrade") != "" &&
- httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade")
- }
|