Commit 914c626c authored by Petar Maymounkov's avatar Petar Maymounkov Committed by Russ Cox

Significant extension to http.Response, which now adheres to the

usage pattern of http.Request and paves the way to persistent connection
handling.

R=rsc
CC=golang-dev
https://golang.org/cl/185043
parent 4f8117d9
......@@ -6,9 +6,11 @@ include ../../Make.$(GOARCH)
TARG=http
GOFILES=\
chunked.go\
client.go\
fs.go\
request.go\
response.go\
server.go\
status.go\
url.go\
......
// 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.
package http
import (
"io"
"os"
"strconv"
)
// NewChunkedWriter returns a new writer that translates writes into HTTP
// "chunked" format before writing them to w. Closing the returned writer
// sends the final 0-length chunk that marks the end of the stream.
func NewChunkedWriter(w io.Writer) io.WriteCloser {
return &chunkedWriter{w}
}
// Writing to ChunkedWriter translates to writing in HTTP chunked Transfer
// Encoding wire format to the undering Wire writer.
type chunkedWriter struct {
Wire io.Writer
}
// Write the contents of data as one chunk to Wire.
// NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has
// a bug since it does not check for success of io.WriteString
func (cw *chunkedWriter) Write(data []byte) (n int, err os.Error) {
// Don't send 0-length data. It looks like EOF for chunked encoding.
if len(data) == 0 {
return 0, nil
}
head := strconv.Itob(len(data), 16) + "\r\n"
if _, err = io.WriteString(cw.Wire, head); err != nil {
return 0, err
}
if n, err = cw.Wire.Write(data); err != nil {
return
}
if n != len(data) {
err = io.ErrShortWrite
return
}
_, err = io.WriteString(cw.Wire, "\r\n")
return
}
func (cw *chunkedWriter) Close() os.Error {
_, err := io.WriteString(cw.Wire, "0\r\n")
return err
}
......@@ -13,49 +13,9 @@ import (
"io"
"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, _ = 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, "]") }
......@@ -68,43 +28,6 @@ type readClose struct {
io.Closer
}
// ReadResponse reads and returns an HTTP response from r.
func ReadResponse(r *bufio.Reader) (*Response, os.Error) {
resp := new(Response)
// Parse the first line of the response.
resp.Header = make(map[string]string)
line, err := readLine(r)
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(r)
if err != nil {
return nil, err
}
if key == "" {
break // end of response header
}
resp.AddHeader(key, value)
}
return resp, nil
}
// Send issues an HTTP request. Caller should close resp.Body when done reading it.
//
// TODO: support persistent connections (multiple requests on a single connection).
......@@ -141,23 +64,13 @@ func send(req *Request) (resp *Response, err os.Error) {
}
reader := bufio.NewReader(conn)
resp, err = ReadResponse(reader)
resp, err = ReadResponse(reader, req.Method)
if err != nil {
conn.Close()
return nil, err
}
r := io.Reader(reader)
if v := resp.GetHeader("Transfer-Encoding"); v == "chunked" {
r = newChunkedReader(reader)
} else if v := resp.GetHeader("Content-Length"); v != "" {
n, err := strconv.Atoi64(v)
if err != nil {
return nil, &badStringError{"invalid Content-Length", v}
}
r = io.LimitReader(r, n)
}
resp.Body = readClose{r, conn}
resp.Body = readClose{resp.Body, conn}
return
}
......@@ -180,8 +93,8 @@ func shouldRedirect(statusCode int) bool {
// 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.
// 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) {
......
......@@ -37,6 +37,9 @@ var (
ErrLineTooLong = &ProtocolError{"header line too long"}
ErrHeaderTooLong = &ProtocolError{"header too long"}
ErrShortBody = &ProtocolError{"entity body too short"}
ErrNotSupported = &ProtocolError{"feature not supported"}
ErrUnexpectedTrailer = &ProtocolError{"trailer header without chunked transfer encoding"}
ErrMissingContentLength = &ProtocolError{"missing ContentLength in HEAD response"}
)
type badStringError struct {
......
This diff is collapsed.
......@@ -126,7 +126,7 @@ func DialHTTP(network, address string) (*Client, os.Error) {
// Require successful HTTP response
// before switching to RPC protocol.
resp, err := http.ReadResponse(bufio.NewReader(conn))
resp, err := http.ReadResponse(bufio.NewReader(conn), "CONNECT")
if err == nil && resp.Status == connected {
return NewClient(conn), nil
}
......
......@@ -90,7 +90,7 @@ func handshake(resourceName, host, origin, location, protocol string, br *bufio.
}
bw.WriteString("\r\n")
bw.Flush()
resp, err := http.ReadResponse(br)
resp, err := http.ReadResponse(br, "GET")
if err != nil {
return
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment