summaryrefslogtreecommitdiff
path: root/src/pkg/net/http/response.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/net/http/response.go')
-rw-r--r--src/pkg/net/http/response.go68
1 files changed, 63 insertions, 5 deletions
diff --git a/src/pkg/net/http/response.go b/src/pkg/net/http/response.go
index 35d0ba3bb..5d2c39080 100644
--- a/src/pkg/net/http/response.go
+++ b/src/pkg/net/http/response.go
@@ -8,6 +8,8 @@ package http
import (
"bufio"
+ "bytes"
+ "crypto/tls"
"errors"
"io"
"net/textproto"
@@ -45,7 +47,8 @@ type Response struct {
//
// The http Client and Transport guarantee that Body is always
// non-nil, even on responses without a body or responses with
- // a zero-lengthed body.
+ // a zero-length body. It is the caller's responsibility to
+ // close Body.
//
// The Body is automatically dechunked if the server replied
// with a "chunked" Transfer-Encoding.
@@ -74,6 +77,12 @@ type Response struct {
// 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.
@@ -141,6 +150,9 @@ func ReadResponse(r *bufio.Reader, req *Request) (*Response, error) {
// 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)
@@ -187,8 +199,8 @@ func (r *Response) ProtoAtLeast(major, minor int) bool {
// ContentLength
// Header, values for non-canonical keys will have unpredictable behavior
//
+// Body is closed after it is sent.
func (r *Response) Write(w io.Writer) error {
-
// Status line
text := r.Status
if text == "" {
@@ -201,10 +213,45 @@ func (r *Response) Write(w io.Writer) error {
protoMajor, protoMinor := strconv.Itoa(r.ProtoMajor), strconv.Itoa(r.ProtoMinor)
statusCode := strconv.Itoa(r.StatusCode) + " "
text = strings.TrimPrefix(text, statusCode)
- io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n")
+ if _, err := io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n"); 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 = eofReader
+ } 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.Close = true
+ }
// Process Body,ContentLength,Close,Trailer
- tw, err := newTransferWriter(r)
+ tw, err := newTransferWriter(r1)
if err != nil {
return err
}
@@ -219,8 +266,19 @@ func (r *Response) Write(w io.Writer) error {
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 {
+ if _, err := io.WriteString(w, "Content-Length: 0\r\n"); err != nil {
+ return err
+ }
+ }
+
// End-of-header
- io.WriteString(w, "\r\n")
+ if _, err := io.WriteString(w, "\r\n"); err != nil {
+ return err
+ }
// Write body and trailer
err = tw.WriteBody(w)