summaryrefslogtreecommitdiff
path: root/libgo/go/http
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/http')
-rw-r--r--libgo/go/http/client.go216
-rw-r--r--libgo/go/http/client_test.go26
-rw-r--r--libgo/go/http/fs.go4
-rw-r--r--libgo/go/http/fs_test.go13
-rw-r--r--libgo/go/http/header.go43
-rw-r--r--libgo/go/http/persist.go136
-rw-r--r--libgo/go/http/proxy_test.go45
-rw-r--r--libgo/go/http/readrequest_test.go18
-rw-r--r--libgo/go/http/request.go208
-rw-r--r--libgo/go/http/request_test.go20
-rw-r--r--libgo/go/http/requestwrite_test.go20
-rw-r--r--libgo/go/http/response.go78
-rw-r--r--libgo/go/http/response_test.go59
-rw-r--r--libgo/go/http/responsewrite_test.go6
-rw-r--r--libgo/go/http/serve_test.go214
-rw-r--r--libgo/go/http/server.go232
-rw-r--r--libgo/go/http/transfer.go90
-rw-r--r--libgo/go/http/transport.go147
18 files changed, 1110 insertions, 465 deletions
diff --git a/libgo/go/http/client.go b/libgo/go/http/client.go
index 022f4f124a8..b1fe5ec6780 100644
--- a/libgo/go/http/client.go
+++ b/libgo/go/http/client.go
@@ -7,18 +7,41 @@
package http
import (
- "bufio"
"bytes"
- "crypto/tls"
"encoding/base64"
"fmt"
"io"
- "net"
"os"
"strconv"
"strings"
)
+// A Client is an HTTP client. Its zero value (DefaultClient) is a usable client
+// that uses DefaultTransport.
+// Client is not yet very configurable.
+type Client struct {
+ Transport ClientTransport // if nil, DefaultTransport is used
+}
+
+// DefaultClient is the default Client and is used by Get, Head, and Post.
+var DefaultClient = &Client{}
+
+// ClientTransport is an interface representing the ability to execute a
+// single HTTP transaction, obtaining the Response for a given Request.
+type ClientTransport interface {
+ // Do executes a single HTTP transaction, returning the Response for the
+ // request req. Do should not attempt to interpret the response.
+ // In particular, Do must return err == nil if it obtained a response,
+ // regardless of the response's HTTP status code. A non-nil err should
+ // be reserved for failure to obtain a response. Similarly, Do should
+ // not attempt to handle higher-level protocol details such as redirects,
+ // authentication, or cookies.
+ //
+ // Transports may modify the request. The request Headers field is
+ // guaranteed to be initalized.
+ Do(req *Request) (resp *Response, err os.Error)
+}
+
// 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, "]") }
@@ -31,67 +54,83 @@ type readClose struct {
io.Closer
}
-// Send issues an HTTP request. Caller should close resp.Body when done reading it.
+// matchNoProxy returns true if requests to addr should not use a proxy,
+// according to the NO_PROXY or no_proxy environment variable.
+func matchNoProxy(addr string) bool {
+ if len(addr) == 0 {
+ return false
+ }
+ no_proxy := os.Getenv("NO_PROXY")
+ if len(no_proxy) == 0 {
+ no_proxy = os.Getenv("no_proxy")
+ }
+ if no_proxy == "*" {
+ return true
+ }
+
+ addr = strings.ToLower(strings.TrimSpace(addr))
+ if hasPort(addr) {
+ addr = addr[:strings.LastIndex(addr, ":")]
+ }
+
+ for _, p := range strings.Split(no_proxy, ",", -1) {
+ p = strings.ToLower(strings.TrimSpace(p))
+ if len(p) == 0 {
+ continue
+ }
+ if hasPort(p) {
+ p = p[:strings.LastIndex(p, ":")]
+ }
+ if addr == p || (p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:])) {
+ return true
+ }
+ }
+ return false
+}
+
+// Do sends an HTTP request and returns an HTTP response, following
+// policy (e.g. redirects, cookies, auth) as configured on the client.
+//
+// Callers should close resp.Body when done reading from it.
+//
+// Generally Get, Post, or PostForm will be used instead of Do.
+func (c *Client) Do(req *Request) (resp *Response, err os.Error) {
+ return send(req, c.Transport)
+}
+
+
+// send issues an HTTP request. Caller should close resp.Body when done reading from 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" && req.URL.Scheme != "https" {
- return nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme}
+func send(req *Request, t ClientTransport) (resp *Response, err os.Error) {
+ if t == nil {
+ t = DefaultTransport
+ if t == nil {
+ err = os.NewError("no http.Client.Transport or http.DefaultTransport")
+ return
+ }
}
- addr := req.URL.Host
- if !hasPort(addr) {
- addr += ":" + req.URL.Scheme
+ // Most the callers of send (Get, Post, et al) don't need
+ // Headers, leaving it uninitialized. We guarantee to the
+ // ClientTransport that this has been initialized, though.
+ if req.Header == nil {
+ req.Header = Header(make(map[string][]string))
}
+
info := req.URL.RawUserinfo
if len(info) > 0 {
enc := base64.URLEncoding
encoded := make([]byte, enc.EncodedLen(len(info)))
enc.Encode(encoded, []byte(info))
if req.Header == nil {
- req.Header = make(map[string]string)
+ req.Header = make(Header)
}
- req.Header["Authorization"] = "Basic " + string(encoded)
- }
-
- var conn io.ReadWriteCloser
- if req.URL.Scheme == "http" {
- conn, err = net.Dial("tcp", "", addr)
- if err != nil {
- return nil, err
- }
- } else { // https
- conn, err = tls.Dial("tcp", "", addr, nil)
- if err != nil {
- return nil, err
- }
- h := req.URL.Host
- if hasPort(h) {
- h = h[0:strings.LastIndex(h, ":")]
- }
- if err := conn.(*tls.Conn).VerifyHostname(h); err != nil {
- return nil, err
- }
- }
-
- err = req.Write(conn)
- if err != nil {
- conn.Close()
- return nil, err
+ req.Header.Set("Authorization", "Basic "+string(encoded))
}
-
- reader := bufio.NewReader(conn)
- resp, err = ReadResponse(reader, req.Method)
- if err != nil {
- conn.Close()
- return nil, err
- }
-
- resp.Body = readClose{resp.Body, conn}
-
- return
+ return t.Do(req)
}
// True if the specified HTTP status code is one for which the Get utility should
@@ -115,12 +154,32 @@ func shouldRedirect(statusCode int) bool {
// 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.
+// Caller should close r.Body when done reading from it.
+//
+// Get is a convenience wrapper around DefaultClient.Get.
func Get(url string) (r *Response, finalURL string, err os.Error) {
+ return DefaultClient.Get(url)
+}
+
+// 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 from it.
+func (c *Client) 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.
var base *URL
+ // TODO: remove this hard-coded 10 and use the Client's policy
+ // (ClientConfig) instead.
for redirect := 0; ; redirect++ {
if redirect >= 10 {
err = os.ErrorString("stopped after 10 redirects")
@@ -128,6 +187,9 @@ func Get(url string) (r *Response, finalURL string, err os.Error) {
}
var req Request
+ req.Method = "GET"
+ req.ProtoMajor = 1
+ req.ProtoMinor = 1
if base == nil {
req.URL, err = ParseURL(url)
} else {
@@ -137,12 +199,12 @@ func Get(url string) (r *Response, finalURL string, err os.Error) {
break
}
url = req.URL.String()
- if r, err = send(&req); err != nil {
+ if r, err = send(&req, c.Transport); err != nil {
break
}
if shouldRedirect(r.StatusCode) {
r.Body.Close()
- if url = r.GetHeader("Location"); url == "" {
+ if url = r.Header.Get("Location"); url == "" {
err = os.ErrorString(fmt.Sprintf("%d response missing Location header", r.StatusCode))
break
}
@@ -159,16 +221,25 @@ func Get(url string) (r *Response, finalURL string, err os.Error) {
// Post issues a POST to the specified URL.
//
-// Caller should close r.Body when done reading it.
+// Caller should close r.Body when done reading from it.
+//
+// Post is a wrapper around DefaultClient.Post
func Post(url string, bodyType string, body io.Reader) (r *Response, err os.Error) {
+ return DefaultClient.Post(url, bodyType, body)
+}
+
+// Post issues a POST to the specified URL.
+//
+// Caller should close r.Body when done reading from it.
+func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err os.Error) {
var req Request
req.Method = "POST"
req.ProtoMajor = 1
req.ProtoMinor = 1
req.Close = true
req.Body = nopCloser{body}
- req.Header = map[string]string{
- "Content-Type": bodyType,
+ req.Header = Header{
+ "Content-Type": {bodyType},
}
req.TransferEncoding = []string{"chunked"}
@@ -177,14 +248,24 @@ func Post(url string, bodyType string, body io.Reader) (r *Response, err os.Erro
return nil, err
}
- return send(&req)
+ return send(&req, c.Transport)
}
// PostForm issues a POST to the specified URL,
// with data's keys and values urlencoded as the request body.
//
-// Caller should close r.Body when done reading it.
+// Caller should close r.Body when done reading from it.
+//
+// PostForm is a wrapper around DefaultClient.PostForm
func PostForm(url string, data map[string]string) (r *Response, err os.Error) {
+ return DefaultClient.PostForm(url, data)
+}
+
+// PostForm issues a POST to the specified URL,
+// with data's keys and values urlencoded as the request body.
+//
+// Caller should close r.Body when done reading from it.
+func (c *Client) PostForm(url string, data map[string]string) (r *Response, err os.Error) {
var req Request
req.Method = "POST"
req.ProtoMajor = 1
@@ -192,9 +273,9 @@ func PostForm(url string, data map[string]string) (r *Response, err os.Error) {
req.Close = true
body := urlencode(data)
req.Body = nopCloser{body}
- req.Header = map[string]string{
- "Content-Type": "application/x-www-form-urlencoded",
- "Content-Length": strconv.Itoa(body.Len()),
+ req.Header = Header{
+ "Content-Type": {"application/x-www-form-urlencoded"},
+ "Content-Length": {strconv.Itoa(body.Len())},
}
req.ContentLength = int64(body.Len())
@@ -203,7 +284,7 @@ func PostForm(url string, data map[string]string) (r *Response, err os.Error) {
return nil, err
}
- return send(&req)
+ return send(&req, c.Transport)
}
// TODO: remove this function when PostForm takes a multimap.
@@ -216,17 +297,20 @@ func urlencode(data map[string]string) (b *bytes.Buffer) {
}
// Head issues a HEAD to the specified URL.
+//
+// Head is a wrapper around DefaultClient.Head
func Head(url string) (r *Response, err os.Error) {
+ return DefaultClient.Head(url)
+}
+
+// Head issues a HEAD to the specified URL.
+func (c *Client) Head(url string) (r *Response, err os.Error) {
var req Request
req.Method = "HEAD"
if req.URL, err = ParseURL(url); err != nil {
return
}
- url = req.URL.String()
- if r, err = send(&req); err != nil {
- return
- }
- return
+ return send(&req, c.Transport)
}
type nopCloser struct {
diff --git a/libgo/go/http/client_test.go b/libgo/go/http/client_test.go
index 013653a8296..c89ecbce2d0 100644
--- a/libgo/go/http/client_test.go
+++ b/libgo/go/http/client_test.go
@@ -8,6 +8,7 @@ package http
import (
"io/ioutil"
+ "os"
"strings"
"testing"
)
@@ -38,3 +39,28 @@ func TestClientHead(t *testing.T) {
t.Error("Last-Modified header not found.")
}
}
+
+type recordingTransport struct {
+ req *Request
+}
+
+func (t *recordingTransport) Do(req *Request) (resp *Response, err os.Error) {
+ t.req = req
+ return nil, os.NewError("dummy impl")
+}
+
+func TestGetRequestFormat(t *testing.T) {
+ tr := &recordingTransport{}
+ client := &Client{Transport: tr}
+ url := "http://dummy.faketld/"
+ client.Get(url) // Note: doesn't hit network
+ if tr.req.Method != "GET" {
+ t.Errorf("expected method %q; got %q", "GET", tr.req.Method)
+ }
+ if tr.req.URL.String() != url {
+ t.Errorf("expected URL %q; got %q", url, tr.req.URL.String())
+ }
+ if tr.req.Header == nil {
+ t.Errorf("expected non-nil request Header")
+ }
+}
diff --git a/libgo/go/http/fs.go b/libgo/go/http/fs.go
index bbfa58d264d..8e16992e0f0 100644
--- a/libgo/go/http/fs.go
+++ b/libgo/go/http/fs.go
@@ -104,7 +104,7 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
}
}
- if t, _ := time.Parse(TimeFormat, r.Header["If-Modified-Since"]); t != nil && d.Mtime_ns/1e9 <= t.Seconds() {
+ if t, _ := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); t != nil && d.Mtime_ns/1e9 <= t.Seconds() {
w.WriteHeader(StatusNotModified)
return
}
@@ -153,7 +153,7 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
// handle Content-Range header.
// TODO(adg): handle multiple ranges
- ranges, err := parseRange(r.Header["Range"], size)
+ ranges, err := parseRange(r.Header.Get("Range"), size)
if err != nil || len(ranges) > 1 {
Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
return
diff --git a/libgo/go/http/fs_test.go b/libgo/go/http/fs_test.go
index 0a5636b88d1..a8b67e3f08c 100644
--- a/libgo/go/http/fs_test.go
+++ b/libgo/go/http/fs_test.go
@@ -109,7 +109,7 @@ func TestServeFile(t *testing.T) {
// set up the Request (re-used for all tests)
var req Request
- req.Header = make(map[string]string)
+ req.Header = make(Header)
if req.URL, err = ParseURL("http://" + serverAddr + "/ServeFile"); err != nil {
t.Fatal("ParseURL:", err)
}
@@ -123,9 +123,9 @@ func TestServeFile(t *testing.T) {
// Range tests
for _, rt := range ServeFileRangeTests {
- req.Header["Range"] = "bytes=" + rt.r
+ req.Header.Set("Range", "bytes="+rt.r)
if rt.r == "" {
- req.Header["Range"] = ""
+ req.Header["Range"] = nil
}
r, body := getBody(t, req)
if r.StatusCode != rt.code {
@@ -138,8 +138,9 @@ func TestServeFile(t *testing.T) {
if rt.r == "" {
h = ""
}
- if r.Header["Content-Range"] != h {
- t.Errorf("header mismatch: range=%q: got %q, want %q", rt.r, r.Header["Content-Range"], h)
+ cr := r.Header.Get("Content-Range")
+ if cr != h {
+ t.Errorf("header mismatch: range=%q: got %q, want %q", rt.r, cr, h)
}
if !equal(body, file[rt.start:rt.end]) {
t.Errorf("body mismatch: range=%q: got %q, want %q", rt.r, body, file[rt.start:rt.end])
@@ -148,7 +149,7 @@ func TestServeFile(t *testing.T) {
}
func getBody(t *testing.T, req Request) (*Response, []byte) {
- r, err := send(&req)
+ r, err := send(&req, DefaultTransport)
if err != nil {
t.Fatal(req.URL.String(), "send:", err)
}
diff --git a/libgo/go/http/header.go b/libgo/go/http/header.go
new file mode 100644
index 00000000000..95b0f3db6bb
--- /dev/null
+++ b/libgo/go/http/header.go
@@ -0,0 +1,43 @@
+// Copyright 2010 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 "net/textproto"
+
+// A Header represents the key-value pairs in an HTTP header.
+type Header map[string][]string
+
+// Add adds the key, value pair to the header.
+// It appends to any existing values associated with key.
+func (h Header) Add(key, value string) {
+ textproto.MIMEHeader(h).Add(key, value)
+}
+
+// Set sets the header entries associated with key to
+// the single element value. It replaces any existing
+// values associated with key.
+func (h Header) Set(key, value string) {
+ textproto.MIMEHeader(h).Set(key, value)
+}
+
+// Get gets the first value associated with the given key.
+// If there are no values associated with the key, Get returns "".
+// Get is a convenience method. For more complex queries,
+// access the map directly.
+func (h Header) Get(key string) string {
+ return textproto.MIMEHeader(h).Get(key)
+}
+
+// Del deletes the values associated with key.
+func (h Header) Del(key string) {
+ textproto.MIMEHeader(h).Del(key)
+}
+
+// CanonicalHeaderKey returns the canonical format of the
+// header key s. The canonicalization converts the first
+// letter and any letter following a hyphen to upper case;
+// the rest are converted to lowercase. For example, the
+// canonical key for "accept-encoding" is "Accept-Encoding".
+func CanonicalHeaderKey(s string) string { return textproto.CanonicalMIMEHeaderKey(s) }
diff --git a/libgo/go/http/persist.go b/libgo/go/http/persist.go
index 8bfc0975589..000a4200e59 100644
--- a/libgo/go/http/persist.go
+++ b/libgo/go/http/persist.go
@@ -6,14 +6,17 @@ package http
import (
"bufio"
- "container/list"
"io"
"net"
+ "net/textproto"
"os"
"sync"
)
-var ErrPersistEOF = &ProtocolError{"persistent connection closed"}
+var (
+ ErrPersistEOF = &ProtocolError{"persistent connection closed"}
+ ErrPipeline = &ProtocolError{"pipeline error"}
+)
// A ServerConn reads requests and sends responses over an underlying
// connection, until the HTTP keepalive logic commands an end. ServerConn
@@ -26,8 +29,10 @@ type ServerConn struct {
r *bufio.Reader
clsd bool // indicates a graceful close
re, we os.Error // read/write errors
- lastBody io.ReadCloser
+ lastbody io.ReadCloser
nread, nwritten int
+ pipe textproto.Pipeline
+ pipereq map[*Request]uint
lk sync.Mutex // protected read/write to re,we
}
@@ -37,7 +42,7 @@ func NewServerConn(c net.Conn, r *bufio.Reader) *ServerConn {
if r == nil {
r = bufio.NewReader(c)
}
- return &ServerConn{c: c, r: r}
+ return &ServerConn{c: c, r: r, pipereq: make(map[*Request]uint)}
}
// Close detaches the ServerConn and returns the underlying connection as well
@@ -57,10 +62,25 @@ func (sc *ServerConn) Close() (c net.Conn, r *bufio.Reader) {
// Read returns the next request on the wire. An ErrPersistEOF is returned if
// it is gracefully determined that there are no more requests (e.g. after the
// first request on an HTTP/1.0 connection, or after a Connection:close on a
-// HTTP/1.1 connection). Read can be called concurrently with Write, but not
-// with another Read.
+// HTTP/1.1 connection).
func (sc *ServerConn) Read() (req *Request, err os.Error) {
+ // Ensure ordered execution of Reads and Writes
+ id := sc.pipe.Next()
+ sc.pipe.StartRequest(id)
+ defer func() {
+ sc.pipe.EndRequest(id)
+ if req == nil {
+ sc.pipe.StartResponse(id)
+ sc.pipe.EndResponse(id)
+ } else {
+ // Remember the pipeline id of this request
+ sc.lk.Lock()
+ sc.pipereq[req] = id
+ sc.lk.Unlock()
+ }
+ }()
+
sc.lk.Lock()
if sc.we != nil { // no point receiving if write-side broken or closed
defer sc.lk.Unlock()
@@ -73,12 +93,12 @@ func (sc *ServerConn) Read() (req *Request, err os.Error) {
sc.lk.Unlock()
// Make sure body is fully consumed, even if user does not call body.Close
- if sc.lastBody != nil {
+ if sc.lastbody != nil {
// body.Close is assumed to be idempotent and multiple calls to
// it should return the error that its first invokation
// returned.
- err = sc.lastBody.Close()
- sc.lastBody = nil
+ err = sc.lastbody.Close()
+ sc.lastbody = nil
if err != nil {
sc.lk.Lock()
defer sc.lk.Unlock()
@@ -102,7 +122,7 @@ func (sc *ServerConn) Read() (req *Request, err os.Error) {
return
}
}
- sc.lastBody = req.Body
+ sc.lastbody = req.Body
sc.nread++
if req.Close {
sc.lk.Lock()
@@ -121,11 +141,24 @@ func (sc *ServerConn) Pending() int {
return sc.nread - sc.nwritten
}
-// Write writes a repsonse. To close the connection gracefully, set the
+// Write writes resp in response to req. To close the connection gracefully, set the
// Response.Close field to true. Write should be considered operational until
// it returns an error, regardless of any errors returned on the Read side.
-// Write can be called concurrently with Read, but not with another Write.
-func (sc *ServerConn) Write(resp *Response) os.Error {
+func (sc *ServerConn) Write(req *Request, resp *Response) os.Error {
+
+ // Retrieve the pipeline ID of this request/response pair
+ sc.lk.Lock()
+ id, ok := sc.pipereq[req]
+ sc.pipereq[req] = 0, false
+ if !ok {
+ sc.lk.Unlock()
+ return ErrPipeline
+ }
+ sc.lk.Unlock()
+
+ // Ensure pipeline order
+ sc.pipe.StartResponse(id)
+ defer sc.pipe.EndResponse(id)
sc.lk.Lock()
if sc.we != nil {
@@ -166,10 +199,11 @@ type ClientConn struct {
c net.Conn
r *bufio.Reader
re, we os.Error // read/write errors
- lastBody io.ReadCloser
+ lastbody io.ReadCloser
nread, nwritten int
- reqm list.List // request methods in order of execution
- lk sync.Mutex // protects read/write to reqm,re,we
+ pipe textproto.Pipeline
+ pipereq map[*Request]uint
+ lk sync.Mutex // protects read/write to re,we,pipereq,etc.
}
// NewClientConn returns a new ClientConn reading and writing c. If r is not
@@ -178,7 +212,7 @@ func NewClientConn(c net.Conn, r *bufio.Reader) *ClientConn {
if r == nil {
r = bufio.NewReader(c)
}
- return &ClientConn{c: c, r: r}
+ return &ClientConn{c: c, r: r, pipereq: make(map[*Request]uint)}
}
// Close detaches the ClientConn and returns the underlying connection as well
@@ -191,7 +225,6 @@ func (cc *ClientConn) Close() (c net.Conn, r *bufio.Reader) {
r = cc.r
cc.c = nil
cc.r = nil
- cc.reqm.Init()
cc.lk.Unlock()
return
}
@@ -201,8 +234,23 @@ func (cc *ClientConn) Close() (c net.Conn, r *bufio.Reader) {
// keepalive connection is logically closed after this request and the opposing
// server is informed. An ErrUnexpectedEOF indicates the remote closed the
// underlying TCP connection, which is usually considered as graceful close.
-// Write can be called concurrently with Read, but not with another Write.
-func (cc *ClientConn) Write(req *Request) os.Error {
+func (cc *ClientConn) Write(req *Request) (err os.Error) {
+
+ // Ensure ordered execution of Writes
+ id := cc.pipe.Next()
+ cc.pipe.StartRequest(id)
+ defer func() {
+ cc.pipe.EndRequest(id)
+ if err != nil {
+ cc.pipe.StartResponse(id)
+ cc.pipe.EndResponse(id)
+ } else {
+ // Remember the pipeline id of this request
+ cc.lk.Lock()
+ cc.pipereq[req] = id
+ cc.lk.Unlock()
+ }
+ }()
cc.lk.Lock()
if cc.re != nil { // no point sending if read-side closed or broken
@@ -223,7 +271,7 @@ func (cc *ClientConn) Write(req *Request) os.Error {
cc.lk.Unlock()
}
- err := req.Write(cc.c)
+ err = req.Write(cc.c)
if err != nil {
cc.lk.Lock()
defer cc.lk.Unlock()
@@ -231,9 +279,6 @@ func (cc *ClientConn) Write(req *Request) os.Error {
return err
}
cc.nwritten++
- cc.lk.Lock()
- cc.reqm.PushBack(req.Method)
- cc.lk.Unlock()
return nil
}
@@ -250,7 +295,21 @@ func (cc *ClientConn) Pending() int {
// returned together with an ErrPersistEOF, which means that the remote
// requested that this be the last request serviced. Read can be called
// concurrently with Write, but not with another Read.
-func (cc *ClientConn) Read() (resp *Response, err os.Error) {
+func (cc *ClientConn) Read(req *Request) (resp *Response, err os.Error) {
+
+ // Retrieve the pipeline ID of this request/response pair
+ cc.lk.Lock()
+ id, ok := cc.pipereq[req]
+ cc.pipereq[req] = 0, false
+ if !ok {
+ cc.lk.Unlock()
+ return nil, ErrPipeline
+ }
+ cc.lk.Unlock()
+
+ // Ensure pipeline order
+ cc.pipe.StartResponse(id)
+ defer cc.pipe.EndResponse(id)
cc.lk.Lock()
if cc.re != nil {
@@ -259,17 +318,13 @@ func (cc *ClientConn) Read() (resp *Response, err os.Error) {
}
cc.lk.Unlock()
- if cc.nread >= cc.nwritten {
- return nil, os.NewError("persist client pipe count")
- }
-
// Make sure body is fully consumed, even if user does not call body.Close
- if cc.lastBody != nil {
+ if cc.lastbody != nil {
// body.Close is assumed to be idempotent and multiple calls to
// it should return the error that its first invokation
// returned.
- err = cc.lastBody.Close()
- cc.lastBody = nil
+ err = cc.lastbody.Close()
+ cc.lastbody = nil
if err != nil {
cc.lk.Lock()
defer cc.lk.Unlock()
@@ -278,18 +333,14 @@ func (cc *ClientConn) Read() (resp *Response, err os.Error) {
}
}
- cc.lk.Lock()
- m := cc.reqm.Front()
- cc.reqm.Remove(m)
- cc.lk.Unlock()
- resp, err = ReadResponse(cc.r, m.Value.(string))
+ resp, err = ReadResponse(cc.r, req.Method)
if err != nil {
cc.lk.Lock()
defer cc.lk.Unlock()
cc.re = err
return
}
- cc.lastBody = resp.Body
+ cc.lastbody = resp.Body
cc.nread++
@@ -301,3 +352,12 @@ func (cc *ClientConn) Read() (resp *Response, err os.Error) {
}
return
}
+
+// Do is convenience method that writes a request and reads a response.
+func (cc *ClientConn) Do(req *Request) (resp *Response, err os.Error) {
+ err = cc.Write(req)
+ if err != nil {
+ return
+ }
+ return cc.Read(req)
+}
diff --git a/libgo/go/http/proxy_test.go b/libgo/go/http/proxy_test.go
new file mode 100644
index 00000000000..0f2ca458fed
--- /dev/null
+++ b/libgo/go/http/proxy_test.go
@@ -0,0 +1,45 @@
+// 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 (
+ "os"
+ "testing"
+)
+
+// TODO(mattn):
+// test ProxyAuth
+
+var MatchNoProxyTests = []struct {
+ host string
+ match bool
+}{
+ {"localhost", true}, // match completely
+ {"barbaz.net", true}, // match as .barbaz.net
+ {"foobar.com:443", true}, // have a port but match
+ {"foofoobar.com", false}, // not match as a part of foobar.com
+ {"baz.com", false}, // not match as a part of barbaz.com
+ {"localhost.net", false}, // not match as suffix of address
+ {"local.localhost", false}, // not match as prefix as address
+ {"barbarbaz.net", false}, // not match because NO_PROXY have a '.'
+ {"www.foobar.com", false}, // not match because NO_PROXY is not .foobar.com
+}
+
+func TestMatchNoProxy(t *testing.T) {
+ oldenv := os.Getenv("NO_PROXY")
+ no_proxy := "foobar.com, .barbaz.net , localhost"
+ os.Setenv("NO_PROXY", no_proxy)
+ defer os.Setenv("NO_PROXY", oldenv)
+
+ for _, test := range MatchNoProxyTests {
+ if matchNoProxy(test.host) != test.match {
+ if test.match {
+ t.Errorf("matchNoProxy(%v) = %v, want %v", test.host, !test.match, test.match)
+ } else {
+ t.Errorf("not expected: '%s' shouldn't match as '%s'", test.host, no_proxy)
+ }
+ }
+ }
+}
diff --git a/libgo/go/http/readrequest_test.go b/libgo/go/http/readrequest_test.go
index 5e1cbcbcbdc..6ee07bc9148 100644
--- a/libgo/go/http/readrequest_test.go
+++ b/libgo/go/http/readrequest_test.go
@@ -50,14 +50,14 @@ var reqTests = []reqTest{
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
- Header: map[string]string{
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
- "Accept-Language": "en-us,en;q=0.5",
- "Accept-Encoding": "gzip,deflate",
- "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
- "Keep-Alive": "300",
- "Proxy-Connection": "keep-alive",
- "Content-Length": "7",
+ Header: Header{
+ "Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
+ "Accept-Language": {"en-us,en;q=0.5"},
+ "Accept-Encoding": {"gzip,deflate"},
+ "Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
+ "Keep-Alive": {"300"},
+ "Proxy-Connection": {"keep-alive"},
+ "Content-Length": {"7"},
},
Close: false,
ContentLength: 7,
@@ -93,7 +93,7 @@ var reqTests = []reqTest{
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
- Header: map[string]string{},
+ Header: map[string][]string{},
Close: false,
ContentLength: -1,
Host: "test",
diff --git a/libgo/go/http/request.go b/libgo/go/http/request.go
index 04bebaaf55b..a7dc328a007 100644
--- a/libgo/go/http/request.go
+++ b/libgo/go/http/request.go
@@ -11,13 +11,13 @@ package http
import (
"bufio"
- "bytes"
"container/vector"
"fmt"
"io"
"io/ioutil"
"mime"
"mime/multipart"
+ "net/textproto"
"os"
"strconv"
"strings"
@@ -90,7 +90,7 @@ type Request struct {
// The request parser implements this by canonicalizing the
// name, making the first character and any characters
// following a hyphen uppercase and the rest lowercase.
- Header map[string]string
+ Header Header
// The message body.
Body io.ReadCloser
@@ -133,7 +133,7 @@ type Request struct {
// Trailer maps trailer keys to values. Like for Header, if the
// response has multiple trailer lines with the same key, they will be
// concatenated, delimited by commas.
- Trailer map[string]string
+ Trailer Header
}
// ProtoAtLeast returns whether the HTTP protocol used
@@ -146,8 +146,8 @@ func (r *Request) ProtoAtLeast(major, minor int) bool {
// MultipartReader returns a MIME multipart reader if this is a
// multipart/form-data POST request, else returns nil and an error.
func (r *Request) MultipartReader() (multipart.Reader, os.Error) {
- v, ok := r.Header["Content-Type"]
- if !ok {
+ v := r.Header.Get("Content-Type")
+ if v == "" {
return nil, ErrNotMultipart
}
d, params := mime.ParseMediaType(v)
@@ -184,6 +184,17 @@ const defaultUserAgent = "Go http package"
// If Body is present, Write forces "Transfer-Encoding: chunked" as a header
// and then closes Body when finished sending it.
func (req *Request) Write(w io.Writer) os.Error {
+ return req.write(w, false)
+}
+
+// WriteProxy is like Write but writes the request in the form
+// expected by an HTTP proxy. It includes the scheme and host
+// name in the URI instead of using a separate Host: header line.
+func (req *Request) WriteProxy(w io.Writer) os.Error {
+ return req.write(w, true)
+}
+
+func (req *Request) write(w io.Writer, usingProxy bool) os.Error {
host := req.Host
if host == "" {
host = req.URL.Host
@@ -197,10 +208,19 @@ func (req *Request) Write(w io.Writer) os.Error {
}
}
+ if usingProxy {
+ if uri == "" || uri[0] != '/' {
+ uri = "/" + uri
+ }
+ uri = req.URL.Scheme + "://" + host + uri
+ }
+
fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), uri)
// Header lines
- fmt.Fprintf(w, "Host: %s\r\n", host)
+ if !usingProxy {
+ fmt.Fprintf(w, "Host: %s\r\n", host)
+ }
fmt.Fprintf(w, "User-Agent: %s\r\n", valueOrDefault(req.UserAgent, defaultUserAgent))
if req.Referer != "" {
fmt.Fprintf(w, "Referer: %s\r\n", req.Referer)
@@ -277,78 +297,6 @@ func readLine(b *bufio.Reader) (s string, err os.Error) {
return string(p), nil
}
-var colon = []byte{':'}
-
-// Read a key/value pair from b.
-// A key/value has the form Key: Value\r\n
-// and the Value can continue on multiple lines if each continuation line
-// starts with a space.
-func readKeyValue(b *bufio.Reader) (key, value string, err os.Error) {
- line, e := readLineBytes(b)
- if e != nil {
- return "", "", e
- }
- if len(line) == 0 {
- return "", "", nil
- }
-
- // Scan first line for colon.
- i := bytes.Index(line, colon)
- if i < 0 {
- goto Malformed
- }
-
- key = string(line[0:i])
- if strings.Contains(key, " ") {
- // Key field has space - no good.
- goto Malformed
- }
-
- // Skip initial space before value.
- for i++; i < len(line); i++ {
- if line[i] != ' ' {
- break
- }
- }
- value = string(line[i:])
-
- // Look for extension lines, which must begin with space.
- for {
- c, e := b.ReadByte()
- if c != ' ' {
- if e != os.EOF {
- b.UnreadByte()
- }
- break
- }
-
- // Eat leading space.
- for c == ' ' {
- if c, e = b.ReadByte(); e != nil {
- if e == os.EOF {
- e = io.ErrUnexpectedEOF
- }
- return "", "", e
- }
- }
- b.UnreadByte()
-
- // Read the rest of the line and add to value.
- if line, e = readLineBytes(b); e != nil {
- return "", "", e
- }
- value += " " + string(line)
-
- if len(value) >= maxValueLength {
- return "", "", &badStringError{"value too long for key", key}
- }
- }
- return key, value, nil
-
-Malformed:
- return "", "", &badStringError{"malformed header line", string(line)}
-}
-
// Convert decimal at s[i:len(s)] to integer,
// returning value, string position where the digits stopped,
// and whether there was a valid number (digits, not too big).
@@ -367,8 +315,9 @@ func atoi(s string, i int) (n, i1 int, ok bool) {
return n, i, true
}
-// Parse HTTP version: "HTTP/1.2" -> (1, 2, true).
-func parseHTTPVersion(vers string) (int, int, bool) {
+// ParseHTTPVersion parses a HTTP version string.
+// "HTTP/1.0" returns (1, 0, true).
+func ParseHTTPVersion(vers string) (major, minor int, ok bool) {
if len(vers) < 5 || vers[0:5] != "HTTP/" {
return 0, 0, false
}
@@ -376,7 +325,6 @@ func parseHTTPVersion(vers string) (int, int, bool) {
if !ok || i >= len(vers) || vers[i] != '.' {
return 0, 0, false
}
- var minor int
minor, i, ok = atoi(vers, i+1)
if !ok || i != len(vers) {
return 0, 0, false
@@ -384,43 +332,6 @@ func parseHTTPVersion(vers string) (int, int, bool) {
return major, minor, true
}
-// CanonicalHeaderKey returns the canonical format of the
-// HTTP header key s. The canonicalization converts the first
-// letter and any letter following a hyphen to upper case;
-// the rest are converted to lowercase. For example, the
-// canonical key for "accept-encoding" is "Accept-Encoding".
-func CanonicalHeaderKey(s string) string {
- // canonicalize: first letter upper case
- // and upper case after each dash.
- // (Host, User-Agent, If-Modified-Since).
- // HTTP headers are ASCII only, so no Unicode issues.
- var a []byte
- upper := true
- for i := 0; i < len(s); i++ {
- v := s[i]
- if upper && 'a' <= v && v <= 'z' {
- if a == nil {
- a = []byte(s)
- }
- a[i] = v + 'A' - 'a'
- }
- if !upper && 'A' <= v && v <= 'Z' {
- if a == nil {
- a = []byte(s)
- }
- a[i] = v + 'a' - 'A'
- }
- upper = false
- if v == '-' {
- upper = true
- }
- }
- if a != nil {
- return string(a)
- }
- return s
-}
-
type chunkedReader struct {
r *bufio.Reader
n uint64 // unread bytes in chunk
@@ -486,11 +397,16 @@ func (cr *chunkedReader) Read(b []uint8) (n int, err os.Error) {
// ReadRequest reads and parses a request from b.
func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {
+
+ tp := textproto.NewReader(b)
req = new(Request)
// First line: GET /index.html HTTP/1.0
var s string
- if s, err = readLine(b); err != nil {
+ if s, err = tp.ReadLine(); err != nil {
+ if err == os.EOF {
+ err = io.ErrUnexpectedEOF
+ }
return nil, err
}
@@ -500,7 +416,7 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {
}
req.Method, req.RawURL, req.Proto = f[0], f[1], f[2]
var ok bool
- if req.ProtoMajor, req.ProtoMinor, ok = parseHTTPVersion(req.Proto); !ok {
+ if req.ProtoMajor, req.ProtoMinor, ok = ParseHTTPVersion(req.Proto); !ok {
return nil, &badStringError{"malformed HTTP version", req.Proto}
}
@@ -509,32 +425,11 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {
}
// Subsequent lines: Key: value.
- nheader := 0
- req.Header = make(map[string]string)
- for {
- var key, value string
- if key, value, err = readKeyValue(b); err != nil {
- return nil, err
- }
- if key == "" {
- break
- }
- if nheader++; nheader >= maxHeaderLines {
- return nil, ErrHeaderTooLong
- }
-
- key = CanonicalHeaderKey(key)
-
- // RFC 2616 says that if you send the same header key
- // multiple times, it has to be semantically equivalent
- // to concatenating the values separated by commas.
- oldvalue, present := req.Header[key]
- if present {
- req.Header[key] = oldvalue + "," + value
- } else {
- req.Header[key] = value
- }
+ mimeHeader, err := tp.ReadMIMEHeader()
+ if err != nil {
+ return nil, err
}
+ req.Header = Header(mimeHeader)
// RFC2616: Must treat
// GET /index.html HTTP/1.1
@@ -545,18 +440,18 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {
// the same. In the second case, any Host line is ignored.
req.Host = req.URL.Host
if req.Host == "" {
- req.Host = req.Header["Host"]
+ req.Host = req.Header.Get("Host")
}
- req.Header["Host"] = "", false
+ req.Header.Del("Host")
fixPragmaCacheControl(req.Header)
// Pull out useful fields as a convenience to clients.
- req.Referer = req.Header["Referer"]
- req.Header["Referer"] = "", false
+ req.Referer = req.Header.Get("Referer")
+ req.Header.Del("Referer")
- req.UserAgent = req.Header["User-Agent"]
- req.Header["User-Agent"] = "", false
+ req.UserAgent = req.Header.Get("User-Agent")
+ req.Header.Del("User-Agent")
// TODO: Parse specific header values:
// Accept
@@ -642,7 +537,7 @@ func (r *Request) ParseForm() (err os.Error) {
if r.Body == nil {
return os.ErrorString("missing form body")
}
- ct := r.Header["Content-Type"]
+ ct := r.Header.Get("Content-Type")
switch strings.Split(ct, ";", 2)[0] {
case "text/plain", "application/x-www-form-urlencoded", "":
b, e := ioutil.ReadAll(r.Body)
@@ -677,17 +572,12 @@ func (r *Request) FormValue(key string) string {
}
func (r *Request) expectsContinue() bool {
- expectation, ok := r.Header["Expect"]
- return ok && strings.ToLower(expectation) == "100-continue"
+ return strings.ToLower(r.Header.Get("Expect")) == "100-continue"
}
func (r *Request) wantsHttp10KeepAlive() bool {
if r.ProtoMajor != 1 || r.ProtoMinor != 0 {
return false
}
- value, exists := r.Header["Connection"]
- if !exists {
- return false
- }
- return strings.Contains(strings.ToLower(value), "keep-alive")
+ return strings.Contains(strings.ToLower(r.Header.Get("Connection")), "keep-alive")
}
diff --git a/libgo/go/http/request_test.go b/libgo/go/http/request_test.go
index d25e5e5e7e1..ae1c4e98245 100644
--- a/libgo/go/http/request_test.go
+++ b/libgo/go/http/request_test.go
@@ -74,7 +74,9 @@ func TestQuery(t *testing.T) {
func TestPostQuery(t *testing.T) {
req := &Request{Method: "POST"}
req.URL, _ = ParseURL("http://www.google.com/search?q=foo&q=bar&both=x")
- req.Header = map[string]string{"Content-Type": "application/x-www-form-urlencoded; boo!"}
+ req.Header = Header{
+ "Content-Type": {"application/x-www-form-urlencoded; boo!"},
+ }
req.Body = nopCloser{strings.NewReader("z=post&both=y")}
if q := req.FormValue("q"); q != "foo" {
t.Errorf(`req.FormValue("q") = %q, want "foo"`, q)
@@ -87,18 +89,18 @@ func TestPostQuery(t *testing.T) {
}
}
-type stringMap map[string]string
+type stringMap map[string][]string
type parseContentTypeTest struct {
contentType stringMap
error bool
}
var parseContentTypeTests = []parseContentTypeTest{
- {contentType: stringMap{"Content-Type": "text/plain"}},
- {contentType: stringMap{"Content-Type": ""}},
- {contentType: stringMap{"Content-Type": "text/plain; boundary="}},
+ {contentType: stringMap{"Content-Type": {"text/plain"}}},
+ {contentType: stringMap{}}, // Non-existent keys are not placed. The value nil is illegal.
+ {contentType: stringMap{"Content-Type": {"text/plain; boundary="}}},
{
- contentType: stringMap{"Content-Type": "application/unknown"},
+ contentType: stringMap{"Content-Type": {"application/unknown"}},
error: true,
},
}
@@ -107,7 +109,7 @@ func TestPostContentTypeParsing(t *testing.T) {
for i, test := range parseContentTypeTests {
req := &Request{
Method: "POST",
- Header: test.contentType,
+ Header: Header(test.contentType),
Body: nopCloser{bytes.NewBufferString("body")},
}
err := req.ParseForm()
@@ -123,7 +125,7 @@ func TestPostContentTypeParsing(t *testing.T) {
func TestMultipartReader(t *testing.T) {
req := &Request{
Method: "POST",
- Header: stringMap{"Content-Type": `multipart/form-data; boundary="foo123"`},
+ Header: Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}},
Body: nopCloser{new(bytes.Buffer)},
}
multipart, err := req.MultipartReader()
@@ -131,7 +133,7 @@ func TestMultipartReader(t *testing.T) {
t.Errorf("expected multipart; error: %v", err)
}
- req.Header = stringMap{"Content-Type": "text/plain"}
+ req.Header = Header{"Content-Type": {"text/plain"}}
multipart, err = req.MultipartReader()
if multipart != nil {
t.Errorf("unexpected multipart for text/plain")
diff --git a/libgo/go/http/requestwrite_test.go b/libgo/go/http/requestwrite_test.go
index 3ceabe4ee7a..55ca745d58c 100644
--- a/libgo/go/http/requestwrite_test.go
+++ b/libgo/go/http/requestwrite_test.go
@@ -34,13 +34,13 @@ var reqWriteTests = []reqWriteTest{
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
- Header: map[string]string{
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
- "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
- "Accept-Encoding": "gzip,deflate",
- "Accept-Language": "en-us,en;q=0.5",
- "Keep-Alive": "300",
- "Proxy-Connection": "keep-alive",
+ Header: Header{
+ "Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
+ "Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
+ "Accept-Encoding": {"gzip,deflate"},
+ "Accept-Language": {"en-us,en;q=0.5"},
+ "Keep-Alive": {"300"},
+ "Proxy-Connection": {"keep-alive"},
},
Body: nil,
Close: false,
@@ -53,10 +53,10 @@ var reqWriteTests = []reqWriteTest{
"GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
"Host: www.techcrunch.com\r\n" +
"User-Agent: Fake\r\n" +
+ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
"Accept-Encoding: gzip,deflate\r\n" +
"Accept-Language: en-us,en;q=0.5\r\n" +
- "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
"Keep-Alive: 300\r\n" +
"Proxy-Connection: keep-alive\r\n\r\n",
},
@@ -71,7 +71,7 @@ var reqWriteTests = []reqWriteTest{
},
ProtoMajor: 1,
ProtoMinor: 1,
- Header: map[string]string{},
+ Header: map[string][]string{},
Body: nopCloser{bytes.NewBufferString("abcdef")},
TransferEncoding: []string{"chunked"},
},
@@ -93,7 +93,7 @@ var reqWriteTests = []reqWriteTest{
},
ProtoMajor: 1,
ProtoMinor: 1,
- Header: map[string]string{},
+ Header: map[string][]string{},
Close: true,
Body: nopCloser{bytes.NewBufferString("abcdef")},
TransferEncoding: []string{"chunked"},
diff --git a/libgo/go/http/response.go b/libgo/go/http/response.go
index a24726110c8..3f919c86a3c 100644
--- a/libgo/go/http/response.go
+++ b/libgo/go/http/response.go
@@ -10,6 +10,7 @@ import (
"bufio"
"fmt"
"io"
+ "net/textproto"
"os"
"sort"
"strconv"
@@ -43,7 +44,7 @@ type Response struct {
// omitted from Header.
//
// Keys in the map are canonicalized (see CanonicalHeaderKey).
- Header map[string]string
+ Header Header
// Body represents the response body.
Body io.ReadCloser
@@ -66,7 +67,7 @@ type Response struct {
// Trailer maps trailer keys to values. Like for Header, if the
// response has multiple trailer lines with the same key, they will be
// concatenated, delimited by commas.
- Trailer map[string]string
+ Trailer map[string][]string
}
// ReadResponse reads and returns an HTTP response from r. The RequestMethod
@@ -76,13 +77,17 @@ type Response struct {
// key/value pairs included in the response trailer.
func ReadResponse(r *bufio.Reader, requestMethod string) (resp *Response, err os.Error) {
+ tp := textproto.NewReader(r)
resp = new(Response)
resp.RequestMethod = strings.ToUpper(requestMethod)
// Parse the first line of the response.
- line, err := readLine(r)
+ line, err := tp.ReadLine()
if err != nil {
+ if err == os.EOF {
+ err = io.ErrUnexpectedEOF
+ }
return nil, err
}
f := strings.Split(line, " ", 3)
@@ -101,26 +106,16 @@ func ReadResponse(r *bufio.Reader, requestMethod string) (resp *Response, err os
resp.Proto = f[0]
var ok bool
- if resp.ProtoMajor, resp.ProtoMinor, ok = parseHTTPVersion(resp.Proto); !ok {
+ if resp.ProtoMajor, resp.ProtoMinor, ok = ParseHTTPVersion(resp.Proto); !ok {
return nil, &badStringError{"malformed HTTP version", resp.Proto}
}
// Parse the response headers.
- nheader := 0
- resp.Header = make(map[string]string)
- for {
- key, value, err := readKeyValue(r)
- if err != nil {
- return nil, err
- }
- if key == "" {
- break // end of response header
- }
- if nheader++; nheader >= maxHeaderLines {
- return nil, ErrHeaderTooLong
- }
- resp.AddHeader(key, value)
+ mimeHeader, err := tp.ReadMIMEHeader()
+ if err != nil {
+ return nil, err
}
+ resp.Header = Header(mimeHeader)
fixPragmaCacheControl(resp.Header)
@@ -136,34 +131,14 @@ func ReadResponse(r *bufio.Reader, requestMethod string) (resp *Response, err os
// Pragma: no-cache
// like
// Cache-Control: no-cache
-func fixPragmaCacheControl(header map[string]string) {
- if header["Pragma"] == "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"] = "no-cache"
+ header["Cache-Control"] = []string{"no-cache"}
}
}
}
-// 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
- }
-}
-
-// GetHeader returns the value of the response header with the given key.
-// 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, GetHeader returns an empty string. Keys are not case sensitive.
-func (r *Response) GetHeader(key string) (value string) {
- return r.Header[CanonicalHeaderKey(key)]
-}
-
// ProtoAtLeast returns whether the HTTP protocol used
// in the response is at least major.minor.
func (r *Response) ProtoAtLeast(major, minor int) bool {
@@ -231,20 +206,19 @@ func (resp *Response) Write(w io.Writer) os.Error {
return nil
}
-func writeSortedKeyValue(w io.Writer, kvm map[string]string, exclude map[string]bool) os.Error {
- kva := make([]string, len(kvm))
- i := 0
- for k, v := range kvm {
+func writeSortedKeyValue(w io.Writer, kvm map[string][]string, exclude map[string]bool) os.Error {
+ keys := make([]string, 0, len(kvm))
+ for k := range kvm {
if !exclude[k] {
- kva[i] = fmt.Sprint(k + ": " + v + "\r\n")
- i++
+ keys = append(keys, k)
}
}
- kva = kva[0:i]
- sort.SortStrings(kva)
- for _, l := range kva {
- if _, err := io.WriteString(w, l); err != nil {
- return err
+ sort.SortStrings(keys)
+ for _, k := range keys {
+ for _, v := range kvm[k] {
+ if _, err := fmt.Fprintf(w, "%s: %s\r\n", k, v); err != nil {
+ return err
+ }
}
}
return nil
diff --git a/libgo/go/http/response_test.go b/libgo/go/http/response_test.go
index 89a8c3b44d2..bf63ccb9e96 100644
--- a/libgo/go/http/response_test.go
+++ b/libgo/go/http/response_test.go
@@ -34,8 +34,8 @@ var respTests = []respTest{
ProtoMajor: 1,
ProtoMinor: 0,
RequestMethod: "GET",
- Header: map[string]string{
- "Connection": "close", // TODO(rsc): Delete?
+ Header: Header{
+ "Connection": {"close"}, // TODO(rsc): Delete?
},
Close: true,
ContentLength: -1,
@@ -44,6 +44,47 @@ var respTests = []respTest{
"Body here\n",
},
+ // Unchunked HTTP/1.1 response without Content-Length or
+ // Connection headers.
+ {
+ "HTTP/1.1 200 OK\r\n" +
+ "\r\n" +
+ "Body here\n",
+
+ Response{
+ Status: "200 OK",
+ StatusCode: 200,
+ Proto: "HTTP/1.1",
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ RequestMethod: "GET",
+ Close: true,
+ ContentLength: -1,
+ },
+
+ "Body here\n",
+ },
+
+ // Unchunked HTTP/1.1 204 response without Content-Length.
+ {
+ "HTTP/1.1 204 No Content\r\n" +
+ "\r\n" +
+ "Body should not be read!\n",
+
+ Response{
+ Status: "204 No Content",
+ StatusCode: 204,
+ Proto: "HTTP/1.1",
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ RequestMethod: "GET",
+ Close: false,
+ ContentLength: 0,
+ },
+
+ "",
+ },
+
// Unchunked response with Content-Length.
{
"HTTP/1.0 200 OK\r\n" +
@@ -59,9 +100,9 @@ var respTests = []respTest{
ProtoMajor: 1,
ProtoMinor: 0,
RequestMethod: "GET",
- Header: map[string]string{
- "Connection": "close", // TODO(rsc): Delete?
- "Content-Length": "10", // TODO(rsc): Delete?
+ Header: Header{
+ "Connection": {"close"}, // TODO(rsc): Delete?
+ "Content-Length": {"10"}, // TODO(rsc): Delete?
},
Close: true,
ContentLength: 10,
@@ -87,7 +128,7 @@ var respTests = []respTest{
ProtoMajor: 1,
ProtoMinor: 0,
RequestMethod: "GET",
- Header: map[string]string{},
+ Header: Header{},
Close: true,
ContentLength: -1,
TransferEncoding: []string{"chunked"},
@@ -114,7 +155,7 @@ var respTests = []respTest{
ProtoMajor: 1,
ProtoMinor: 0,
RequestMethod: "GET",
- Header: map[string]string{},
+ Header: Header{},
Close: true,
ContentLength: -1, // TODO(rsc): Fix?
TransferEncoding: []string{"chunked"},
@@ -134,7 +175,7 @@ var respTests = []respTest{
ProtoMajor: 1,
ProtoMinor: 0,
RequestMethod: "GET",
- Header: map[string]string{},
+ Header: Header{},
Close: true,
ContentLength: -1,
},
@@ -153,7 +194,7 @@ var respTests = []respTest{
ProtoMajor: 1,
ProtoMinor: 0,
RequestMethod: "GET",
- Header: map[string]string{},
+ Header: Header{},
Close: true,
ContentLength: -1,
},
diff --git a/libgo/go/http/responsewrite_test.go b/libgo/go/http/responsewrite_test.go
index 9f10be5626d..aabb833f9c8 100644
--- a/libgo/go/http/responsewrite_test.go
+++ b/libgo/go/http/responsewrite_test.go
@@ -22,7 +22,7 @@ var respWriteTests = []respWriteTest{
ProtoMajor: 1,
ProtoMinor: 0,
RequestMethod: "GET",
- Header: map[string]string{},
+ Header: map[string][]string{},
Body: nopCloser{bytes.NewBufferString("abcdef")},
ContentLength: 6,
},
@@ -38,7 +38,7 @@ var respWriteTests = []respWriteTest{
ProtoMajor: 1,
ProtoMinor: 0,
RequestMethod: "GET",
- Header: map[string]string{},
+ Header: map[string][]string{},
Body: nopCloser{bytes.NewBufferString("abcdef")},
ContentLength: -1,
},
@@ -53,7 +53,7 @@ var respWriteTests = []respWriteTest{
ProtoMajor: 1,
ProtoMinor: 1,
RequestMethod: "GET",
- Header: map[string]string{},
+ Header: map[string][]string{},
Body: nopCloser{bytes.NewBufferString("abcdef")},
ContentLength: 6,
TransferEncoding: []string{"chunked"},
diff --git a/libgo/go/http/serve_test.go b/libgo/go/http/serve_test.go
index 053d6dca448..42fe3e5e4d2 100644
--- a/libgo/go/http/serve_test.go
+++ b/libgo/go/http/serve_test.go
@@ -9,10 +9,14 @@ package http
import (
"bufio"
"bytes"
+ "fmt"
"io"
+ "io/ioutil"
"os"
"net"
+ "strings"
"testing"
+ "time"
)
type dummyAddr string
@@ -136,6 +140,71 @@ func TestConsumingBodyOnNextConn(t *testing.T) {
}
}
+type stringHandler string
+
+func (s stringHandler) ServeHTTP(w ResponseWriter, r *Request) {
+ w.SetHeader("Result", string(s))
+}
+
+var handlers = []struct {
+ pattern string
+ msg string
+}{
+ {"/", "Default"},
+ {"/someDir/", "someDir"},
+ {"someHost.com/someDir/", "someHost.com/someDir"},
+}
+
+var vtests = []struct {
+ url string
+ expected string
+}{
+ {"http://localhost/someDir/apage", "someDir"},
+ {"http://localhost/otherDir/apage", "Default"},
+ {"http://someHost.com/someDir/apage", "someHost.com/someDir"},
+ {"http://otherHost.com/someDir/apage", "someDir"},
+ {"http://otherHost.com/aDir/apage", "Default"},
+}
+
+func TestHostHandlers(t *testing.T) {
+ for _, h := range handlers {
+ Handle(h.pattern, stringHandler(h.msg))
+ }
+ l, err := net.Listen("tcp", "127.0.0.1:0") // any port
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer l.Close()
+ go Serve(l, nil)
+ conn, err := net.Dial("tcp", "", l.Addr().String())
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer conn.Close()
+ cc := NewClientConn(conn, nil)
+ for _, vt := range vtests {
+ var r *Response
+ var req Request
+ if req.URL, err = ParseURL(vt.url); err != nil {
+ t.Errorf("cannot parse url: %v", err)
+ continue
+ }
+ if err := cc.Write(&req); err != nil {
+ t.Errorf("writing request: %v", err)
+ continue
+ }
+ r, err := cc.Read(&req)
+ if err != nil {
+ t.Errorf("reading response: %v", err)
+ continue
+ }
+ s := r.Header.Get("Result")
+ if s != vt.expected {
+ t.Errorf("Get(%q) = %q, want %q", vt.url, s, vt.expected)
+ }
+ }
+}
+
type responseWriterMethodCall struct {
method string
headerKey, headerValue string // if method == "SetHeader"
@@ -218,3 +287,148 @@ func TestMuxRedirectLeadingSlashes(t *testing.T) {
}
}
}
+
+func TestServerTimeouts(t *testing.T) {
+ l, err := net.ListenTCP("tcp", &net.TCPAddr{Port: 0})
+ if err != nil {
+ t.Fatalf("listen error: %v", err)
+ }
+ addr, _ := l.Addr().(*net.TCPAddr)
+
+ reqNum := 0
+ handler := HandlerFunc(func(res ResponseWriter, req *Request) {
+ reqNum++
+ fmt.Fprintf(res, "req=%d", reqNum)
+ })
+
+ const second = 1000000000 /* nanos */
+ server := &Server{Handler: handler, ReadTimeout: 0.25 * second, WriteTimeout: 0.25 * second}
+ go server.Serve(l)
+
+ url := fmt.Sprintf("http://localhost:%d/", addr.Port)
+
+ // Hit the HTTP server successfully.
+ r, _, err := Get(url)
+ if err != nil {
+ t.Fatalf("http Get #1: %v", err)
+ }
+ got, _ := ioutil.ReadAll(r.Body)
+ expected := "req=1"
+ if string(got) != expected {
+ t.Errorf("Unexpected response for request #1; got %q; expected %q",
+ string(got), expected)
+ }
+
+ // Slow client that should timeout.
+ t1 := time.Nanoseconds()
+ conn, err := net.Dial("tcp", "", fmt.Sprintf("localhost:%d", addr.Port))
+ if err != nil {
+ t.Fatalf("Dial: %v", err)
+ }
+ buf := make([]byte, 1)
+ n, err := conn.Read(buf)
+ latency := time.Nanoseconds() - t1
+ if n != 0 || err != os.EOF {
+ t.Errorf("Read = %v, %v, wanted %v, %v", n, err, 0, os.EOF)
+ }
+ if latency < second*0.20 /* fudge from 0.25 above */ {
+ t.Errorf("got EOF after %d ns, want >= %d", latency, second*0.20)
+ }
+
+ // Hit the HTTP server successfully again, verifying that the
+ // previous slow connection didn't run our handler. (that we
+ // get "req=2", not "req=3")
+ r, _, err = Get(url)
+ if err != nil {
+ t.Fatalf("http Get #2: %v", err)
+ }
+ got, _ = ioutil.ReadAll(r.Body)
+ expected = "req=2"
+ if string(got) != expected {
+ t.Errorf("Get #2 got %q, want %q", string(got), expected)
+ }
+
+ l.Close()
+}
+
+// TestIdentityResponse verifies that a handler can unset
+func TestIdentityResponse(t *testing.T) {
+ l, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatalf("failed to listen on a port: %v", err)
+ }
+ defer l.Close()
+ urlBase := "http://" + l.Addr().String() + "/"
+
+ handler := HandlerFunc(func(rw ResponseWriter, req *Request) {
+ rw.SetHeader("Content-Length", "3")
+ rw.SetHeader("Transfer-Encoding", req.FormValue("te"))
+ switch {
+ case req.FormValue("overwrite") == "1":
+ _, err := rw.Write([]byte("foo TOO LONG"))
+ if err != ErrContentLength {
+ t.Errorf("expected ErrContentLength; got %v", err)
+ }
+ case req.FormValue("underwrite") == "1":
+ rw.SetHeader("Content-Length", "500")
+ rw.Write([]byte("too short"))
+ default:
+ rw.Write([]byte("foo"))
+ }
+ })
+
+ server := &Server{Handler: handler}
+ go server.Serve(l)
+
+ // Note: this relies on the assumption (which is true) that
+ // Get sends HTTP/1.1 or greater requests. Otherwise the
+ // server wouldn't have the choice to send back chunked
+ // responses.
+ for _, te := range []string{"", "identity"} {
+ url := urlBase + "?te=" + te
+ res, _, err := Get(url)
+ if err != nil {
+ t.Fatalf("error with Get of %s: %v", url, err)
+ }
+ if cl, expected := res.ContentLength, int64(3); cl != expected {
+ t.Errorf("for %s expected res.ContentLength of %d; got %d", url, expected, cl)
+ }
+ if cl, expected := res.Header.Get("Content-Length"), "3"; cl != expected {
+ t.Errorf("for %s expected Content-Length header of %q; got %q", url, expected, cl)
+ }
+ if tl, expected := len(res.TransferEncoding), 0; tl != expected {
+ t.Errorf("for %s expected len(res.TransferEncoding) of %d; got %d (%v)",
+ url, expected, tl, res.TransferEncoding)
+ }
+ }
+
+ // Verify that ErrContentLength is returned
+ url := urlBase + "?overwrite=1"
+ _, _, err = Get(url)
+ if err != nil {
+ t.Fatalf("error with Get of %s: %v", url, err)
+ }
+
+ // Verify that the connection is closed when the declared Content-Length
+ // is larger than what the handler wrote.
+ conn, err := net.Dial("tcp", "", l.Addr().String())
+ if err != nil {
+ t.Fatalf("error dialing: %v", err)
+ }
+ _, err = conn.Write([]byte("GET /?underwrite=1 HTTP/1.1\r\nHost: foo\r\n\r\n"))
+ if err != nil {
+ t.Fatalf("error writing: %v", err)
+ }
+ // The next ReadAll will hang for a failing test, so use a Timer instead
+ // to fail more traditionally
+ timer := time.AfterFunc(2e9, func() {
+ t.Fatalf("Timeout expired in ReadAll.")
+ })
+ defer timer.Stop()
+ got, _ := ioutil.ReadAll(conn)
+ expectedSuffix := "\r\n\r\ntoo short"
+ if !strings.HasSuffix(string(got), expectedSuffix) {
+ t.Fatalf("Expected output to end with %q; got response body %q",
+ expectedSuffix, string(got))
+ }
+}
diff --git a/libgo/go/http/server.go b/libgo/go/http/server.go
index 644724f58e6..977c8c2297a 100644
--- a/libgo/go/http/server.go
+++ b/libgo/go/http/server.go
@@ -31,6 +31,7 @@ var (
ErrWriteAfterFlush = os.NewError("Conn.Write called after Flush")
ErrBodyNotAllowed = os.NewError("http: response status code does not allow body")
ErrHijacked = os.NewError("Conn has been hijacked")
+ ErrContentLength = os.NewError("Conn.Write wrote more than the declared Content-Length")
)
// Objects implementing the Handler interface can be
@@ -60,10 +61,10 @@ type ResponseWriter interface {
//
// Content-Type: text/html; charset=utf-8
//
- // being sent. UTF-8 encoded HTML is the default setting for
+ // being sent. UTF-8 encoded HTML is the default setting for
// Content-Type in this library, so users need not make that
- // particular call. Calls to SetHeader after WriteHeader (or Write)
- // are ignored.
+ // particular call. Calls to SetHeader after WriteHeader (or Write)
+ // are ignored. An empty value removes the header if previously set.
SetHeader(string, string)
// Write writes the data to the connection as part of an HTTP reply.
@@ -108,6 +109,7 @@ type response struct {
wroteContinue bool // 100 Continue response was written
header map[string]string // reply header parameters
written int64 // number of bytes written in body
+ contentLength int64 // explicitly-declared Content-Length; or -1
status int // status code passed to WriteHeader
// close connection after this reply. set on request and
@@ -170,33 +172,13 @@ func (c *conn) readRequest() (w *response, err os.Error) {
w.conn = c
w.req = req
w.header = make(map[string]string)
+ w.contentLength = -1
// Expect 100 Continue support
if req.expectsContinue() && req.ProtoAtLeast(1, 1) {
// Wrap the Body reader with one that replies on the connection
req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
}
-
- // Default output is HTML encoded in UTF-8.
- w.SetHeader("Content-Type", "text/html; charset=utf-8")
- w.SetHeader("Date", time.UTC().Format(TimeFormat))
-
- if req.Method == "HEAD" {
- // do nothing
- } else if req.ProtoAtLeast(1, 1) {
- // HTTP/1.1 or greater: use chunked transfer encoding
- // to avoid closing the connection at EOF.
- w.chunking = true
- w.SetHeader("Transfer-Encoding", "chunked")
- } else {
- // HTTP version < 1.1: cannot do chunked transfer
- // encoding, so signal EOF by closing connection.
- // Will be overridden if the HTTP handler ends up
- // writing a Content-Length and the client requested
- // "Connection: keep-alive"
- w.closeAfterReply = true
- }
-
return w, nil
}
@@ -209,7 +191,10 @@ func (w *response) UsingTLS() bool {
func (w *response) RemoteAddr() string { return w.conn.remoteAddr }
// SetHeader implements the ResponseWriter.SetHeader method
-func (w *response) SetHeader(hdr, val string) { w.header[CanonicalHeaderKey(hdr)] = val }
+// An empty value removes the header from the map.
+func (w *response) SetHeader(hdr, val string) {
+ w.header[CanonicalHeaderKey(hdr)] = val, val != ""
+}
// WriteHeader implements the ResponseWriter.WriteHeader method
func (w *response) WriteHeader(code int) {
@@ -225,13 +210,83 @@ func (w *response) WriteHeader(code int) {
w.status = code
if code == StatusNotModified {
// Must not have body.
- w.header["Content-Type"] = "", false
- w.header["Transfer-Encoding"] = "", false
+ for _, header := range []string{"Content-Type", "Content-Length", "Transfer-Encoding"} {
+ if w.header[header] != "" {
+ // TODO: return an error if WriteHeader gets a return parameter
+ // or set a flag on w to make future Writes() write an error page?
+ // for now just log and drop the header.
+ log.Printf("http: StatusNotModified response with header %q defined", header)
+ w.header[header] = "", false
+ }
+ }
+ } else {
+ // Default output is HTML encoded in UTF-8.
+ if w.header["Content-Type"] == "" {
+ w.SetHeader("Content-Type", "text/html; charset=utf-8")
+ }
+ }
+
+ if w.header["Date"] == "" {
+ w.SetHeader("Date", time.UTC().Format(TimeFormat))
+ }
+
+ // Check for a explicit (and valid) Content-Length header.
+ var hasCL bool
+ var contentLength int64
+ if clenStr, ok := w.header["Content-Length"]; ok {
+ var err os.Error
+ contentLength, err = strconv.Atoi64(clenStr)
+ if err == nil {
+ hasCL = true
+ } else {
+ log.Printf("http: invalid Content-Length of %q sent", clenStr)
+ w.SetHeader("Content-Length", "")
+ }
+ }
+
+ te, hasTE := w.header["Transfer-Encoding"]
+ if hasCL && hasTE && te != "identity" {
+ // TODO: return an error if WriteHeader gets a return parameter
+ // For now just ignore the Content-Length.
+ log.Printf("http: WriteHeader called with both Transfer-Encoding of %q and a Content-Length of %d",
+ te, contentLength)
+ w.SetHeader("Content-Length", "")
+ hasCL = false
+ }
+
+ if w.req.Method == "HEAD" {
+ // do nothing
+ } else if hasCL {
w.chunking = false
+ w.contentLength = contentLength
+ w.SetHeader("Transfer-Encoding", "")
+ } else if w.req.ProtoAtLeast(1, 1) {
+ // HTTP/1.1 or greater: use chunked transfer encoding
+ // to avoid closing the connection at EOF.
+ // TODO: this blows away any custom or stacked Transfer-Encoding they
+ // might have set. Deal with that as need arises once we have a valid
+ // use case.
+ w.chunking = true
+ w.SetHeader("Transfer-Encoding", "chunked")
+ } else {
+ // HTTP version < 1.1: cannot do chunked transfer
+ // encoding and we don't know the Content-Length so
+ // signal EOF by closing connection.
+ w.closeAfterReply = true
+ w.chunking = false // redundant
+ w.SetHeader("Transfer-Encoding", "") // in case already set
+ }
+
+ if w.req.wantsHttp10KeepAlive() && (w.req.Method == "HEAD" || hasCL) {
+ _, connectionHeaderSet := w.header["Connection"]
+ if !connectionHeaderSet {
+ w.SetHeader("Connection", "keep-alive")
+ }
}
+
// Cannot use Content-Length with non-identity Transfer-Encoding.
if w.chunking {
- w.header["Content-Length"] = "", false
+ w.SetHeader("Content-Length", "")
}
if !w.req.ProtoAtLeast(1, 0) {
return
@@ -259,15 +314,6 @@ func (w *response) Write(data []byte) (n int, err os.Error) {
return 0, ErrHijacked
}
if !w.wroteHeader {
- if w.req.wantsHttp10KeepAlive() {
- _, hasLength := w.header["Content-Length"]
- if hasLength {
- _, connectionHeaderSet := w.header["Connection"]
- if !connectionHeaderSet {
- w.header["Connection"] = "keep-alive"
- }
- }
- }
w.WriteHeader(StatusOK)
}
if len(data) == 0 {
@@ -280,6 +326,9 @@ func (w *response) Write(data []byte) (n int, err os.Error) {
}
w.written += int64(len(data)) // ignoring errors, for errorKludge
+ if w.contentLength != -1 && w.written > w.contentLength {
+ return 0, ErrContentLength
+ }
// TODO(rsc): if chunking happened after the buffering,
// then there would be fewer chunk headers.
@@ -369,6 +418,11 @@ func (w *response) finishRequest() {
}
w.conn.buf.Flush()
w.req.Body.Close()
+
+ if w.contentLength != -1 && w.contentLength != w.written {
+ // Did not write enough. Avoid getting out of sync.
+ w.closeAfterReply = true
+ }
}
// Flush implements the ResponseWriter.Flush method.
@@ -539,9 +593,8 @@ func RedirectHandler(url string, code int) Handler {
// patterns and calls the handler for the pattern that
// most closely matches the URL.
//
-// Patterns named fixed paths, like "/favicon.ico",
-// or subtrees, like "/images/" (note the trailing slash).
-// Patterns must begin with /.
+// Patterns named fixed, rooted paths, like "/favicon.ico",
+// or rooted subtrees, like "/images/" (note the trailing slash).
// Longer patterns take precedence over shorter ones, so that
// if there are handlers registered for both "/images/"
// and "/images/thumbnails/", the latter handler will be
@@ -549,11 +602,11 @@ func RedirectHandler(url string, code int) Handler {
// former will receiver requests for any other paths in the
// "/images/" subtree.
//
-// In the future, the pattern syntax may be relaxed to allow
-// an optional host-name at the beginning of the pattern,
-// so that a handler might register for the two patterns
-// "/codesearch" and "codesearch.google.com/"
-// without taking over requests for http://www.google.com/.
+// Patterns may optionally begin with a host name, restricting matches to
+// URLs on that host only. Host-specific patterns take precedence over
+// general patterns, so that a handler might register for the two patterns
+// "/codesearch" and "codesearch.google.com/" without also taking over
+// requests for "http://www.google.com/".
//
// ServeMux also takes care of sanitizing the URL request path,
// redirecting any request containing . or .. elements to an
@@ -598,21 +651,13 @@ func cleanPath(p string) string {
return np
}
-// ServeHTTP dispatches the request to the handler whose
-// pattern most closely matches the request URL.
-func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
- // Clean path to canonical form and redirect.
- if p := cleanPath(r.URL.Path); p != r.URL.Path {
- w.SetHeader("Location", p)
- w.WriteHeader(StatusMovedPermanently)
- return
- }
-
- // Most-specific (longest) pattern wins.
+// Find a handler on a handler map given a path string
+// Most-specific (longest) pattern wins
+func (mux *ServeMux) match(path string) Handler {
var h Handler
var n = 0
for k, v := range mux.m {
- if !pathMatch(k, r.URL.Path) {
+ if !pathMatch(k, path) {
continue
}
if h == nil || len(k) > n {
@@ -620,6 +665,23 @@ func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
h = v
}
}
+ return h
+}
+
+// ServeHTTP dispatches the request to the handler whose
+// pattern most closely matches the request URL.
+func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
+ // Clean path to canonical form and redirect.
+ if p := cleanPath(r.URL.Path); p != r.URL.Path {
+ w.SetHeader("Location", p)
+ w.WriteHeader(StatusMovedPermanently)
+ return
+ }
+ // Host-specific pattern takes precedence over generic ones
+ h := mux.match(r.Host + r.URL.Path)
+ if h == nil {
+ h = mux.match(r.URL.Path)
+ }
if h == nil {
h = NotFoundHandler()
}
@@ -628,7 +690,7 @@ func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
// Handle registers the handler for the given pattern.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
- if pattern == "" || pattern[0] != '/' {
+ if pattern == "" {
panic("http: invalid pattern " + pattern)
}
@@ -649,10 +711,12 @@ func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Re
// Handle registers the handler for the given pattern
// in the DefaultServeMux.
+// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
+// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
@@ -662,6 +726,39 @@ func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
// read requests and then call handler to reply to them.
// Handler is typically nil, in which case the DefaultServeMux is used.
func Serve(l net.Listener, handler Handler) os.Error {
+ srv := &Server{Handler: handler}
+ return srv.Serve(l)
+}
+
+// A Server defines parameters for running an HTTP server.
+type Server struct {
+ Addr string // TCP address to listen on, ":http" if empty
+ Handler Handler // handler to invoke, http.DefaultServeMux if nil
+ ReadTimeout int64 // the net.Conn.SetReadTimeout value for new connections
+ WriteTimeout int64 // the net.Conn.SetWriteTimeout value for new connections
+}
+
+// ListenAndServe listens on the TCP network address srv.Addr and then
+// calls Serve to handle requests on incoming connections. If
+// srv.Addr is blank, ":http" is used.
+func (srv *Server) ListenAndServe() os.Error {
+ addr := srv.Addr
+ if addr == "" {
+ addr = ":http"
+ }
+ l, e := net.Listen("tcp", addr)
+ if e != nil {
+ return e
+ }
+ return srv.Serve(l)
+}
+
+// Serve accepts incoming connections on the Listener l, creating a
+// new service thread for each. The service threads read requests and
+// then call srv.Handler to reply to them.
+func (srv *Server) Serve(l net.Listener) os.Error {
+ defer l.Close()
+ handler := srv.Handler
if handler == nil {
handler = DefaultServeMux
}
@@ -670,6 +767,12 @@ func Serve(l net.Listener, handler Handler) os.Error {
if e != nil {
return e
}
+ if srv.ReadTimeout != 0 {
+ rw.SetReadTimeout(srv.ReadTimeout)
+ }
+ if srv.WriteTimeout != 0 {
+ rw.SetWriteTimeout(srv.WriteTimeout)
+ }
c, err := newConn(rw, handler)
if err != nil {
continue
@@ -703,17 +806,12 @@ func Serve(l net.Listener, handler Handler) os.Error {
// http.HandleFunc("/hello", HelloServer)
// err := http.ListenAndServe(":12345", nil)
// if err != nil {
-// log.Exit("ListenAndServe: ", err.String())
+// log.Fatal("ListenAndServe: ", err.String())
// }
// }
func ListenAndServe(addr string, handler Handler) os.Error {
- l, e := net.Listen("tcp", addr)
- if e != nil {
- return e
- }
- e = Serve(l, handler)
- l.Close()
- return e
+ server := &Server{Addr: addr, Handler: handler}
+ return server.ListenAndServe()
}
// ListenAndServeTLS acts identically to ListenAndServe, except that it
@@ -737,7 +835,7 @@ func ListenAndServe(addr string, handler Handler) os.Error {
// log.Printf("About to listen on 10443. Go to https://127.0.0.1:10443/")
// err := http.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil)
// if err != nil {
-// log.Exit(err)
+// log.Fatal(err)
// }
// }
//
diff --git a/libgo/go/http/transfer.go b/libgo/go/http/transfer.go
index e62885d62fd..996e2897325 100644
--- a/libgo/go/http/transfer.go
+++ b/libgo/go/http/transfer.go
@@ -21,7 +21,7 @@ type transferWriter struct {
ContentLength int64
Close bool
TransferEncoding []string
- Trailer map[string]string
+ Trailer Header
}
func newTransferWriter(r interface{}) (t *transferWriter, err os.Error) {
@@ -159,7 +159,7 @@ func (t *transferWriter) WriteBody(w io.Writer) (err os.Error) {
type transferReader struct {
// Input
- Header map[string]string
+ Header Header
StatusCode int
RequestMethod string
ProtoMajor int
@@ -169,7 +169,21 @@ type transferReader struct {
ContentLength int64
TransferEncoding []string
Close bool
- Trailer map[string]string
+ Trailer Header
+}
+
+// bodyAllowedForStatus returns whether a given response status code
+// permits a body. See RFC2616, section 4.4.
+func bodyAllowedForStatus(status int) bool {
+ switch {
+ case status >= 100 && status <= 199:
+ return false
+ case status == 204:
+ return false
+ case status == 304:
+ return false
+ }
+ return true
}
// msg is *Request or *Response.
@@ -217,6 +231,19 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err os.Error) {
return err
}
+ // If there is no Content-Length or chunked Transfer-Encoding on a *Response
+ // and the status is not 1xx, 204 or 304, then the body is unbounded.
+ // See RFC2616, section 4.4.
+ switch msg.(type) {
+ case *Response:
+ if t.ContentLength == -1 &&
+ !chunked(t.TransferEncoding) &&
+ bodyAllowedForStatus(t.StatusCode) {
+ // Unbounded body.
+ t.Close = true
+ }
+ }
+
// Prepare body reader. ContentLength < 0 means chunked encoding
// or close connection when finished, since multipart is not supported yet
switch {
@@ -262,14 +289,14 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err os.Error) {
func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }
// Sanitize transfer encoding
-func fixTransferEncoding(header map[string]string) ([]string, os.Error) {
+func fixTransferEncoding(header Header) ([]string, os.Error) {
raw, present := header["Transfer-Encoding"]
if !present {
return nil, nil
}
- header["Transfer-Encoding"] = "", false
- encodings := strings.Split(raw, ",", -1)
+ header["Transfer-Encoding"] = nil, false
+ encodings := strings.Split(raw[0], ",", -1)
te := make([]string, 0, len(encodings))
// TODO: Even though we only support "identity" and "chunked"
// encodings, the loop below is designed with foresight. One
@@ -294,7 +321,7 @@ func fixTransferEncoding(header map[string]string) ([]string, os.Error) {
// Chunked encoding trumps Content-Length. See RFC 2616
// Section 4.4. Currently len(te) > 0 implies chunked
// encoding.
- header["Content-Length"] = "", false
+ header["Content-Length"] = nil, false
return te, nil
}
@@ -304,7 +331,7 @@ func fixTransferEncoding(header map[string]string) ([]string, os.Error) {
// Determine the expected body length, using RFC 2616 Section 4.4. This
// function is not a method, because ultimately it should be shared by
// ReadResponse and ReadRequest.
-func fixLength(status int, requestMethod string, header map[string]string, te []string) (int64, os.Error) {
+func fixLength(status int, requestMethod string, header Header, te []string) (int64, os.Error) {
// Logic based on response type or status
if noBodyExpected(requestMethod) {
@@ -324,23 +351,21 @@ func fixLength(status int, requestMethod string, header map[string]string, te []
}
// Logic based on Content-Length
- if cl, present := header["Content-Length"]; present {
- cl = strings.TrimSpace(cl)
- if cl != "" {
- n, err := strconv.Atoi64(cl)
- if err != nil || n < 0 {
- return -1, &badStringError{"bad Content-Length", cl}
- }
- return n, nil
- } else {
- header["Content-Length"] = "", false
+ cl := strings.TrimSpace(header.Get("Content-Length"))
+ if cl != "" {
+ n, err := strconv.Atoi64(cl)
+ if err != nil || n < 0 {
+ return -1, &badStringError{"bad Content-Length", cl}
}
+ return n, nil
+ } else {
+ header.Del("Content-Length")
}
// Logic based on media type. The purpose of the following code is just
// to detect whether the unsupported "multipart/byteranges" is being
// used. A proper Content-Type parser is needed in the future.
- if strings.Contains(strings.ToLower(header["Content-Type"]), "multipart/byteranges") {
+ if strings.Contains(strings.ToLower(header.Get("Content-Type")), "multipart/byteranges") {
return -1, ErrNotSupported
}
@@ -351,24 +376,19 @@ func fixLength(status int, requestMethod string, header map[string]string, te []
// Determine whether to hang up after sending a request and body, or
// receiving a response and body
// 'header' is the request headers
-func shouldClose(major, minor int, header map[string]string) bool {
+func shouldClose(major, minor int, header Header) bool {
if major < 1 {
return true
} else if major == 1 && minor == 0 {
- v, present := header["Connection"]
- if !present {
- return true
- }
- v = strings.ToLower(v)
- if !strings.Contains(v, "keep-alive") {
+ if !strings.Contains(strings.ToLower(header.Get("Connection")), "keep-alive") {
return true
}
return false
- } else if v, present := header["Connection"]; present {
+ } else {
// TODO: Should split on commas, toss surrounding white space,
// and check each field.
- if v == "close" {
- header["Connection"] = "", false
+ if strings.ToLower(header.Get("Connection")) == "close" {
+ header.Del("Connection")
return true
}
}
@@ -376,14 +396,14 @@ func shouldClose(major, minor int, header map[string]string) bool {
}
// Parse the trailer header
-func fixTrailer(header map[string]string, te []string) (map[string]string, os.Error) {
- raw, present := header["Trailer"]
- if !present {
+func fixTrailer(header Header, te []string) (Header, os.Error) {
+ raw := header.Get("Trailer")
+ if raw == "" {
return nil, nil
}
- header["Trailer"] = "", false
- trailer := make(map[string]string)
+ header.Del("Trailer")
+ trailer := make(Header)
keys := strings.Split(raw, ",", -1)
for _, key := range keys {
key = CanonicalHeaderKey(strings.TrimSpace(key))
@@ -391,7 +411,7 @@ func fixTrailer(header map[string]string, te []string) (map[string]string, os.Er
case "Transfer-Encoding", "Trailer", "Content-Length":
return nil, &badStringError{"bad trailer key", key}
}
- trailer[key] = ""
+ trailer.Del(key)
}
if len(trailer) == 0 {
return nil, nil
diff --git a/libgo/go/http/transport.go b/libgo/go/http/transport.go
new file mode 100644
index 00000000000..41d639c7e2f
--- /dev/null
+++ b/libgo/go/http/transport.go
@@ -0,0 +1,147 @@
+// Copyright 2011 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 (
+ "bufio"
+ "crypto/tls"
+ "encoding/base64"
+ "fmt"
+ "net"
+ "os"
+ "strings"
+ "sync"
+)
+
+// DefaultTransport is the default implementation of ClientTransport
+// and is used by DefaultClient. It establishes a new network connection for
+// each call to Do and uses HTTP proxies as directed by the $HTTP_PROXY and
+// $NO_PROXY (or $http_proxy and $no_proxy) environment variables.
+var DefaultTransport ClientTransport = &transport{}
+
+// transport implements http.ClientTranport for the default case,
+// using TCP connections to either the host or a proxy, serving
+// http or https schemes. In the future this may become public
+// and support options on keep-alive connection duration, pipelining
+// controls, etc. For now this is simply a port of the old Go code
+// client code to the http.ClientTransport interface.
+type transport struct {
+ // TODO: keep-alives, pipelining, etc using a map from
+ // scheme/host to a connection. Something like:
+ l sync.Mutex
+ hostConn map[string]*ClientConn
+}
+
+func (ct *transport) Do(req *Request) (resp *Response, err os.Error) {
+ if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
+ return nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme}
+ }
+
+ addr := req.URL.Host
+ if !hasPort(addr) {
+ addr += ":" + req.URL.Scheme
+ }
+
+ var proxyURL *URL
+ proxyAuth := ""
+ proxy := ""
+ if !matchNoProxy(addr) {
+ proxy = os.Getenv("HTTP_PROXY")
+ if proxy == "" {
+ proxy = os.Getenv("http_proxy")
+ }
+ }
+
+ if proxy != "" {
+ proxyURL, err = ParseRequestURL(proxy)
+ if err != nil {
+ return nil, os.ErrorString("invalid proxy address")
+ }
+ if proxyURL.Host == "" {
+ proxyURL, err = ParseRequestURL("http://" + proxy)
+ if err != nil {
+ return nil, os.ErrorString("invalid proxy address")
+ }
+ }
+ addr = proxyURL.Host
+ proxyInfo := proxyURL.RawUserinfo
+ if proxyInfo != "" {
+ enc := base64.URLEncoding
+ encoded := make([]byte, enc.EncodedLen(len(proxyInfo)))
+ enc.Encode(encoded, []byte(proxyInfo))
+ proxyAuth = "Basic " + string(encoded)
+ }
+ }
+
+ // Connect to server or proxy
+ conn, err := net.Dial("tcp", "", addr)
+ if err != nil {
+ return nil, err
+ }
+
+ if req.URL.Scheme == "http" {
+ // Include proxy http header if needed.
+ if proxyAuth != "" {
+ req.Header.Set("Proxy-Authorization", proxyAuth)
+ }
+ } else { // https
+ if proxyURL != nil {
+ // Ask proxy for direct connection to server.
+ // addr defaults above to ":https" but we need to use numbers
+ addr = req.URL.Host
+ if !hasPort(addr) {
+ addr += ":443"
+ }
+ fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\n", addr)
+ fmt.Fprintf(conn, "Host: %s\r\n", addr)
+ if proxyAuth != "" {
+ fmt.Fprintf(conn, "Proxy-Authorization: %s\r\n", proxyAuth)
+ }
+ fmt.Fprintf(conn, "\r\n")
+
+ // Read response.
+ // Okay to use and discard buffered reader here, because
+ // TLS server will not speak until spoken to.
+ br := bufio.NewReader(conn)
+ resp, err := ReadResponse(br, "CONNECT")
+ if err != nil {
+ return nil, err
+ }
+ if resp.StatusCode != 200 {
+ f := strings.Split(resp.Status, " ", 2)
+ return nil, os.ErrorString(f[1])
+ }
+ }
+
+ // Initiate TLS and check remote host name against certificate.
+ conn = tls.Client(conn, nil)
+ if err = conn.(*tls.Conn).Handshake(); err != nil {
+ return nil, err
+ }
+ h := req.URL.Host
+ if hasPort(h) {
+ h = h[:strings.LastIndex(h, ":")]
+ }
+ if err = conn.(*tls.Conn).VerifyHostname(h); err != nil {
+ return nil, err
+ }
+ }
+
+ err = req.Write(conn)
+ if err != nil {
+ conn.Close()
+ return nil, err
+ }
+
+ reader := bufio.NewReader(conn)
+ resp, err = ReadResponse(reader, req.Method)
+ if err != nil {
+ conn.Close()
+ return nil, err
+ }
+
+ resp.Body = readClose{resp.Body, conn}
+ return
+}