Commit 2c420ece authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

http: change ResponseWriter.SetHeader(k,v) to Header() accessor

Caller code needs to change:

rw.SetHeader("Content-Type", "text/plain")
to:
rw.Header().Set("Content-Type", "text/plain")

This now permits returning multiple headers
with the same name using Add:

rw.Header().Add("Set-Cookie", "..")
rw.Header().Add("Set-Cookie", "..")

This patch also fixes serialization of headers, removing newline characters.

Fixes #488
Fixes #914

R=rsc
CC=gburd, golang-dev
https://golang.org/cl/4239076
parent fe8639a9
...@@ -702,7 +702,7 @@ func servePage(w http.ResponseWriter, title, subtitle, query string, content []b ...@@ -702,7 +702,7 @@ func servePage(w http.ResponseWriter, title, subtitle, query string, content []b
func serveText(w http.ResponseWriter, text []byte) { func serveText(w http.ResponseWriter, text []byte) {
w.SetHeader("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Write(text) w.Write(text)
} }
......
...@@ -111,7 +111,7 @@ func exec(rw http.ResponseWriter, args []string) (status int) { ...@@ -111,7 +111,7 @@ func exec(rw http.ResponseWriter, args []string) (status int) {
os.Stderr.Write(buf.Bytes()) os.Stderr.Write(buf.Bytes())
} }
if rw != nil { if rw != nil {
rw.SetHeader("content-type", "text/plain; charset=utf-8") rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
rw.Write(buf.Bytes()) rw.Write(buf.Bytes())
} }
......
...@@ -269,7 +269,7 @@ func Iter() <-chan KeyValue { ...@@ -269,7 +269,7 @@ func Iter() <-chan KeyValue {
} }
func expvarHandler(w http.ResponseWriter, r *http.Request) { func expvarHandler(w http.ResponseWriter, r *http.Request) {
w.SetHeader("content-type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprintf(w, "{\n") fmt.Fprintf(w, "{\n")
first := true first := true
for name, value := range vars { for name, value := range vars {
......
...@@ -149,12 +149,8 @@ func (r *response) RemoteAddr() string { ...@@ -149,12 +149,8 @@ func (r *response) RemoteAddr() string {
return os.Getenv("REMOTE_ADDR") return os.Getenv("REMOTE_ADDR")
} }
func (r *response) SetHeader(k, v string) { func (r *response) Header() http.Header {
if v == "" { return r.header
r.header.Del(k)
} else {
r.header.Set(k, v)
}
} }
func (r *response) Write(p []byte) (n int, err os.Error) { func (r *response) Write(p []byte) (n int, err os.Error) {
......
...@@ -139,7 +139,7 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { ...@@ -139,7 +139,7 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
} }
linebody := line.NewReader(cmd.Stdout, 1024) linebody := line.NewReader(cmd.Stdout, 1024)
headers := make(map[string]string) headers := rw.Header()
statusCode := http.StatusOK statusCode := http.StatusOK
for { for {
line, isPrefix, err := linebody.ReadLine() line, isPrefix, err := linebody.ReadLine()
...@@ -181,12 +181,9 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { ...@@ -181,12 +181,9 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
} }
statusCode = code statusCode = code
default: default:
headers[header] = val headers.Add(header, val)
} }
} }
for h, v := range headers {
rw.SetHeader(h, v)
}
rw.WriteHeader(statusCode) rw.WriteHeader(statusCode)
_, err = io.Copy(rw, linebody) _, err = io.Copy(rw, linebody)
......
...@@ -111,10 +111,10 @@ func TestCGIBasicGet(t *testing.T) { ...@@ -111,10 +111,10 @@ func TestCGIBasicGet(t *testing.T) {
} }
replay := runCgiTest(t, h, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap) replay := runCgiTest(t, h, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
if expected, got := "text/html", replay.Header.Get("Content-Type"); got != expected { if expected, got := "text/html", replay.Header().Get("Content-Type"); got != expected {
t.Errorf("got a Content-Type of %q; expected %q", got, expected) t.Errorf("got a Content-Type of %q; expected %q", got, expected)
} }
if expected, got := "X-Test-Value", replay.Header.Get("X-Test-Header"); got != expected { if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected {
t.Errorf("got a X-Test-Header of %q; expected %q", got, expected) t.Errorf("got a X-Test-Header of %q; expected %q", got, expected)
} }
} }
......
...@@ -43,10 +43,10 @@ func TestHostingOurselves(t *testing.T) { ...@@ -43,10 +43,10 @@ func TestHostingOurselves(t *testing.T) {
} }
replay := runCgiTest(t, h, "GET /test.go?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap) replay := runCgiTest(t, h, "GET /test.go?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
if expected, got := "text/html; charset=utf-8", replay.Header.Get("Content-Type"); got != expected { if expected, got := "text/html; charset=utf-8", replay.Header().Get("Content-Type"); got != expected {
t.Errorf("got a Content-Type of %q; expected %q", got, expected) t.Errorf("got a Content-Type of %q; expected %q", got, expected)
} }
if expected, got := "X-Test-Value", replay.Header.Get("X-Test-Header"); got != expected { if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected {
t.Errorf("got a X-Test-Header of %q; expected %q", got, expected) t.Errorf("got a X-Test-Header of %q; expected %q", got, expected)
} }
} }
...@@ -58,7 +58,7 @@ func TestBeChildCGIProcess(t *testing.T) { ...@@ -58,7 +58,7 @@ func TestBeChildCGIProcess(t *testing.T) {
return return
} }
Serve(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { Serve(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.SetHeader("X-Test-Header", "X-Test-Value") rw.Header().Set("X-Test-Header", "X-Test-Value")
fmt.Fprintf(rw, "test=Hello CGI-in-CGI\n") fmt.Fprintf(rw, "test=Hello CGI-in-CGI\n")
req.ParseForm() req.ParseForm()
for k, vv := range req.Form { for k, vv := range req.Form {
......
...@@ -108,7 +108,7 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) { ...@@ -108,7 +108,7 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
w.WriteHeader(StatusNotModified) w.WriteHeader(StatusNotModified)
return return
} }
w.SetHeader("Last-Modified", time.SecondsToUTC(d.Mtime_ns/1e9).Format(TimeFormat)) w.Header().Set("Last-Modified", time.SecondsToUTC(d.Mtime_ns/1e9).Format(TimeFormat))
// use contents of index.html for directory, if present // use contents of index.html for directory, if present
if d.IsDirectory() { if d.IsDirectory() {
...@@ -137,16 +137,16 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) { ...@@ -137,16 +137,16 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
// use extension to find content type. // use extension to find content type.
ext := filepath.Ext(name) ext := filepath.Ext(name)
if ctype := mime.TypeByExtension(ext); ctype != "" { if ctype := mime.TypeByExtension(ext); ctype != "" {
w.SetHeader("Content-Type", ctype) w.Header().Set("Content-Type", ctype)
} else { } else {
// read first chunk to decide between utf-8 text and binary // read first chunk to decide between utf-8 text and binary
var buf [1024]byte var buf [1024]byte
n, _ := io.ReadFull(f, buf[:]) n, _ := io.ReadFull(f, buf[:])
b := buf[:n] b := buf[:n]
if isText(b) { if isText(b) {
w.SetHeader("Content-Type", "text-plain; charset=utf-8") w.Header().Set("Content-Type", "text-plain; charset=utf-8")
} else { } else {
w.SetHeader("Content-Type", "application/octet-stream") // generic binary w.Header().Set("Content-Type", "application/octet-stream") // generic binary
} }
f.Seek(0, 0) // rewind to output whole file f.Seek(0, 0) // rewind to output whole file
} }
...@@ -166,11 +166,11 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) { ...@@ -166,11 +166,11 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
} }
size = ra.length size = ra.length
code = StatusPartialContent code = StatusPartialContent
w.SetHeader("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size)) w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size))
} }
w.SetHeader("Accept-Ranges", "bytes") w.Header().Set("Accept-Ranges", "bytes")
w.SetHeader("Content-Length", strconv.Itoa64(size)) w.Header().Set("Content-Length", strconv.Itoa64(size))
w.WriteHeader(code) w.WriteHeader(code)
......
...@@ -14,11 +14,10 @@ import ( ...@@ -14,11 +14,10 @@ import (
// ResponseRecorder is an implementation of http.ResponseWriter that // ResponseRecorder is an implementation of http.ResponseWriter that
// records its mutations for later inspection in tests. // records its mutations for later inspection in tests.
type ResponseRecorder struct { type ResponseRecorder struct {
Code int // the HTTP response code from WriteHeader Code int // the HTTP response code from WriteHeader
Header http.Header // if non-nil, the headers to populate HeaderMap http.Header // the HTTP response headers
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
Flushed bool Flushed bool
FakeRemoteAddr string // the fake RemoteAddr to return, or "" for DefaultRemoteAddr FakeRemoteAddr string // the fake RemoteAddr to return, or "" for DefaultRemoteAddr
FakeUsingTLS bool // whether to return true from the UsingTLS method FakeUsingTLS bool // whether to return true from the UsingTLS method
} }
...@@ -26,8 +25,8 @@ type ResponseRecorder struct { ...@@ -26,8 +25,8 @@ type ResponseRecorder struct {
// NewRecorder returns an initialized ResponseRecorder. // NewRecorder returns an initialized ResponseRecorder.
func NewRecorder() *ResponseRecorder { func NewRecorder() *ResponseRecorder {
return &ResponseRecorder{ return &ResponseRecorder{
Header: http.Header(make(map[string][]string)), HeaderMap: make(http.Header),
Body: new(bytes.Buffer), Body: new(bytes.Buffer),
} }
} }
...@@ -49,15 +48,9 @@ func (rw *ResponseRecorder) UsingTLS() bool { ...@@ -49,15 +48,9 @@ func (rw *ResponseRecorder) UsingTLS() bool {
return rw.FakeUsingTLS return rw.FakeUsingTLS
} }
// SetHeader populates rw.Header, if non-nil. // Header returns the response headers.
func (rw *ResponseRecorder) SetHeader(k, v string) { func (rw *ResponseRecorder) Header() http.Header {
if rw.Header != nil { return rw.HeaderMap
if v == "" {
rw.Header.Del(k)
} else {
rw.Header.Set(k, v)
}
}
} }
// Write always succeeds and writes to rw.Body, if not nil. // Write always succeeds and writes to rw.Body, if not nil.
......
...@@ -41,14 +41,14 @@ func init() { ...@@ -41,14 +41,14 @@ func init() {
// command line, with arguments separated by NUL bytes. // command line, with arguments separated by NUL bytes.
// The package initialization registers it as /debug/pprof/cmdline. // The package initialization registers it as /debug/pprof/cmdline.
func Cmdline(w http.ResponseWriter, r *http.Request) { func Cmdline(w http.ResponseWriter, r *http.Request) {
w.SetHeader("content-type", "text/plain; charset=utf-8") w.Header().Set("content-type", "text/plain; charset=utf-8")
fmt.Fprintf(w, strings.Join(os.Args, "\x00")) fmt.Fprintf(w, strings.Join(os.Args, "\x00"))
} }
// Heap responds with the pprof-formatted heap profile. // Heap responds with the pprof-formatted heap profile.
// The package initialization registers it as /debug/pprof/heap. // The package initialization registers it as /debug/pprof/heap.
func Heap(w http.ResponseWriter, r *http.Request) { func Heap(w http.ResponseWriter, r *http.Request) {
w.SetHeader("content-type", "text/plain; charset=utf-8") w.Header().Set("content-type", "text/plain; charset=utf-8")
pprof.WriteHeapProfile(w) pprof.WriteHeapProfile(w)
} }
...@@ -56,7 +56,7 @@ func Heap(w http.ResponseWriter, r *http.Request) { ...@@ -56,7 +56,7 @@ func Heap(w http.ResponseWriter, r *http.Request) {
// responding with a table mapping program counters to function names. // responding with a table mapping program counters to function names.
// The package initialization registers it as /debug/pprof/symbol. // The package initialization registers it as /debug/pprof/symbol.
func Symbol(w http.ResponseWriter, r *http.Request) { func Symbol(w http.ResponseWriter, r *http.Request) {
w.SetHeader("content-type", "text/plain; charset=utf-8") w.Header().Set("content-type", "text/plain; charset=utf-8")
// We don't know how many symbols we have, but we // We don't know how many symbols we have, but we
// do have symbol information. Pprof only cares whether // do have symbol information. Pprof only cares whether
......
...@@ -217,13 +217,16 @@ func (resp *Response) Write(w io.Writer) os.Error { ...@@ -217,13 +217,16 @@ func (resp *Response) Write(w io.Writer) os.Error {
func writeSortedHeader(w io.Writer, h Header, exclude map[string]bool) os.Error { func writeSortedHeader(w io.Writer, h Header, exclude map[string]bool) os.Error {
keys := make([]string, 0, len(h)) keys := make([]string, 0, len(h))
for k := range h { for k := range h {
if !exclude[k] { if exclude == nil || !exclude[k] {
keys = append(keys, k) keys = append(keys, k)
} }
} }
sort.SortStrings(keys) sort.SortStrings(keys)
for _, k := range keys { for _, k := range keys {
for _, v := range h[k] { for _, v := range h[k] {
v = strings.TrimSpace(v)
v = strings.Replace(v, "\n", " ", -1)
v = strings.Replace(v, "\r", " ", -1)
if _, err := fmt.Fprintf(w, "%s: %s\r\n", k, v); err != nil { if _, err := fmt.Fprintf(w, "%s: %s\r\n", k, v); err != nil {
return err return err
} }
......
...@@ -65,6 +65,29 @@ var respWriteTests = []respWriteTest{ ...@@ -65,6 +65,29 @@ var respWriteTests = []respWriteTest{
"Transfer-Encoding: chunked\r\n\r\n" + "Transfer-Encoding: chunked\r\n\r\n" +
"6\r\nabcdef\r\n0\r\n\r\n", "6\r\nabcdef\r\n0\r\n\r\n",
}, },
// Header value with a newline character (Issue 914).
// Also tests removal of leading and trailing whitespace.
{
Response{
StatusCode: 204,
ProtoMajor: 1,
ProtoMinor: 1,
RequestMethod: "GET",
Header: Header{
"Foo": []string{" Bar\nBaz "},
},
Body: nil,
ContentLength: 0,
TransferEncoding: []string{"chunked"},
Close: true,
},
"HTTP/1.1 204 No Content\r\n" +
"Connection: close\r\n" +
"Foo: Bar Baz\r\n" +
"\r\n",
},
} }
func TestResponseWrite(t *testing.T) { func TestResponseWrite(t *testing.T) {
...@@ -78,7 +101,7 @@ func TestResponseWrite(t *testing.T) { ...@@ -78,7 +101,7 @@ func TestResponseWrite(t *testing.T) {
} }
sraw := braw.String() sraw := braw.String()
if sraw != tt.Raw { if sraw != tt.Raw {
t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.Raw, sraw) t.Errorf("Test %d, expecting:\n%q\nGot:\n%q\n", i, tt.Raw, sraw)
continue continue
} }
} }
......
...@@ -144,7 +144,7 @@ func TestConsumingBodyOnNextConn(t *testing.T) { ...@@ -144,7 +144,7 @@ func TestConsumingBodyOnNextConn(t *testing.T) {
type stringHandler string type stringHandler string
func (s stringHandler) ServeHTTP(w ResponseWriter, r *Request) { func (s stringHandler) ServeHTTP(w ResponseWriter, r *Request) {
w.SetHeader("Result", string(s)) w.Header().Set("Result", string(s))
} }
var handlers = []struct { var handlers = []struct {
...@@ -216,7 +216,7 @@ func TestMuxRedirectLeadingSlashes(t *testing.T) { ...@@ -216,7 +216,7 @@ func TestMuxRedirectLeadingSlashes(t *testing.T) {
mux.ServeHTTP(resp, req) mux.ServeHTTP(resp, req)
if loc, expected := resp.Header.Get("Location"), "/foo.txt"; loc != expected { if loc, expected := resp.Header().Get("Location"), "/foo.txt"; loc != expected {
t.Errorf("Expected Location header set to %q; got %q", expected, loc) t.Errorf("Expected Location header set to %q; got %q", expected, loc)
return return
} }
...@@ -294,8 +294,8 @@ func TestServerTimeouts(t *testing.T) { ...@@ -294,8 +294,8 @@ func TestServerTimeouts(t *testing.T) {
// TestIdentityResponse verifies that a handler can unset // TestIdentityResponse verifies that a handler can unset
func TestIdentityResponse(t *testing.T) { func TestIdentityResponse(t *testing.T) {
handler := HandlerFunc(func(rw ResponseWriter, req *Request) { handler := HandlerFunc(func(rw ResponseWriter, req *Request) {
rw.SetHeader("Content-Length", "3") rw.Header().Set("Content-Length", "3")
rw.SetHeader("Transfer-Encoding", req.FormValue("te")) rw.Header().Set("Transfer-Encoding", req.FormValue("te"))
switch { switch {
case req.FormValue("overwrite") == "1": case req.FormValue("overwrite") == "1":
_, err := rw.Write([]byte("foo TOO LONG")) _, err := rw.Write([]byte("foo TOO LONG"))
...@@ -303,7 +303,7 @@ func TestIdentityResponse(t *testing.T) { ...@@ -303,7 +303,7 @@ func TestIdentityResponse(t *testing.T) {
t.Errorf("expected ErrContentLength; got %v", err) t.Errorf("expected ErrContentLength; got %v", err)
} }
case req.FormValue("underwrite") == "1": case req.FormValue("underwrite") == "1":
rw.SetHeader("Content-Length", "500") rw.Header().Set("Content-Length", "500")
rw.Write([]byte("too short")) rw.Write([]byte("too short"))
default: default:
rw.Write([]byte("foo")) rw.Write([]byte("foo"))
......
...@@ -54,17 +54,10 @@ type ResponseWriter interface { ...@@ -54,17 +54,10 @@ type ResponseWriter interface {
// UsingTLS returns true if the client is connected using TLS // UsingTLS returns true if the client is connected using TLS
UsingTLS() bool UsingTLS() bool
// SetHeader sets a header line in the eventual response. // Header returns the header map that will be sent by WriteHeader.
// For example, SetHeader("Content-Type", "text/html; charset=utf-8") // Changing the header after a call to WriteHeader (or Write) has
// will result in the header line // no effect.
// Header() Header
// Content-Type: text/html; charset=utf-8
//
// 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. 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. // Write writes the data to the connection as part of an HTTP reply.
// If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK) // If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK)
...@@ -106,14 +99,14 @@ type conn struct { ...@@ -106,14 +99,14 @@ type conn struct {
// A response represents the server side of an HTTP response. // A response represents the server side of an HTTP response.
type response struct { type response struct {
conn *conn conn *conn
req *Request // request for this response req *Request // request for this response
chunking bool // using chunked transfer encoding for reply body chunking bool // using chunked transfer encoding for reply body
wroteHeader bool // reply header has been written wroteHeader bool // reply header has been written
wroteContinue bool // 100 Continue response was written wroteContinue bool // 100 Continue response was written
header map[string]string // reply header parameters header Header // reply header parameters
written int64 // number of bytes written in body written int64 // number of bytes written in body
contentLength int64 // explicitly-declared Content-Length; or -1 contentLength int64 // explicitly-declared Content-Length; or -1
status int // status code passed to WriteHeader status int // status code passed to WriteHeader
// close connection after this reply. set on request and // close connection after this reply. set on request and
// updated after response from handler if there's a // updated after response from handler if there's a
...@@ -174,7 +167,7 @@ func (c *conn) readRequest() (w *response, err os.Error) { ...@@ -174,7 +167,7 @@ func (c *conn) readRequest() (w *response, err os.Error) {
w = new(response) w = new(response)
w.conn = c w.conn = c
w.req = req w.req = req
w.header = make(map[string]string) w.header = make(Header)
w.contentLength = -1 w.contentLength = -1
// Expect 100 Continue support // Expect 100 Continue support
...@@ -185,21 +178,16 @@ func (c *conn) readRequest() (w *response, err os.Error) { ...@@ -185,21 +178,16 @@ func (c *conn) readRequest() (w *response, err os.Error) {
return w, nil return w, nil
} }
// UsingTLS implements the ResponseWriter.UsingTLS
func (w *response) UsingTLS() bool { func (w *response) UsingTLS() bool {
return w.conn.usingTLS return w.conn.usingTLS
} }
// RemoteAddr implements the ResponseWriter.RemoteAddr method
func (w *response) RemoteAddr() string { return w.conn.remoteAddr } func (w *response) RemoteAddr() string { return w.conn.remoteAddr }
// SetHeader implements the ResponseWriter.SetHeader method func (w *response) Header() Header {
// An empty value removes the header from the map. return w.header
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) { func (w *response) WriteHeader(code int) {
if w.conn.hijacked { if w.conn.hijacked {
log.Print("http: response.WriteHeader on hijacked connection") log.Print("http: response.WriteHeader on hijacked connection")
...@@ -214,46 +202,47 @@ func (w *response) WriteHeader(code int) { ...@@ -214,46 +202,47 @@ func (w *response) WriteHeader(code int) {
if code == StatusNotModified { if code == StatusNotModified {
// Must not have body. // Must not have body.
for _, header := range []string{"Content-Type", "Content-Length", "Transfer-Encoding"} { for _, header := range []string{"Content-Type", "Content-Length", "Transfer-Encoding"} {
if w.header[header] != "" { if w.header.Get(header) != "" {
// TODO: return an error if WriteHeader gets a return parameter // TODO: return an error if WriteHeader gets a return parameter
// or set a flag on w to make future Writes() write an error page? // or set a flag on w to make future Writes() write an error page?
// for now just log and drop the header. // for now just log and drop the header.
log.Printf("http: StatusNotModified response with header %q defined", header) log.Printf("http: StatusNotModified response with header %q defined", header)
w.header[header] = "", false w.header.Del(header)
} }
} }
} else { } else {
// Default output is HTML encoded in UTF-8. // Default output is HTML encoded in UTF-8.
if w.header["Content-Type"] == "" { if w.header.Get("Content-Type") == "" {
w.SetHeader("Content-Type", "text/html; charset=utf-8") w.header.Set("Content-Type", "text/html; charset=utf-8")
} }
} }
if w.header["Date"] == "" { if w.header.Get("Date") == "" {
w.SetHeader("Date", time.UTC().Format(TimeFormat)) w.Header().Set("Date", time.UTC().Format(TimeFormat))
} }
// Check for a explicit (and valid) Content-Length header. // Check for a explicit (and valid) Content-Length header.
var hasCL bool var hasCL bool
var contentLength int64 var contentLength int64
if clenStr, ok := w.header["Content-Length"]; ok { if clenStr := w.header.Get("Content-Length"); clenStr != "" {
var err os.Error var err os.Error
contentLength, err = strconv.Atoi64(clenStr) contentLength, err = strconv.Atoi64(clenStr)
if err == nil { if err == nil {
hasCL = true hasCL = true
} else { } else {
log.Printf("http: invalid Content-Length of %q sent", clenStr) log.Printf("http: invalid Content-Length of %q sent", clenStr)
w.SetHeader("Content-Length", "") w.header.Set("Content-Length", "")
} }
} }
te, hasTE := w.header["Transfer-Encoding"] te := w.header.Get("Transfer-Encoding")
hasTE := te != ""
if hasCL && hasTE && te != "identity" { if hasCL && hasTE && te != "identity" {
// TODO: return an error if WriteHeader gets a return parameter // TODO: return an error if WriteHeader gets a return parameter
// For now just ignore the Content-Length. // For now just ignore the Content-Length.
log.Printf("http: WriteHeader called with both Transfer-Encoding of %q and a Content-Length of %d", log.Printf("http: WriteHeader called with both Transfer-Encoding of %q and a Content-Length of %d",
te, contentLength) te, contentLength)
w.SetHeader("Content-Length", "") w.header.Set("Content-Length", "")
hasCL = false hasCL = false
} }
...@@ -262,7 +251,7 @@ func (w *response) WriteHeader(code int) { ...@@ -262,7 +251,7 @@ func (w *response) WriteHeader(code int) {
} else if hasCL { } else if hasCL {
w.chunking = false w.chunking = false
w.contentLength = contentLength w.contentLength = contentLength
w.SetHeader("Transfer-Encoding", "") w.header.Del("Transfer-Encoding")
} else if w.req.ProtoAtLeast(1, 1) { } else if w.req.ProtoAtLeast(1, 1) {
// HTTP/1.1 or greater: use chunked transfer encoding // HTTP/1.1 or greater: use chunked transfer encoding
// to avoid closing the connection at EOF. // to avoid closing the connection at EOF.
...@@ -270,20 +259,20 @@ func (w *response) WriteHeader(code int) { ...@@ -270,20 +259,20 @@ func (w *response) WriteHeader(code int) {
// might have set. Deal with that as need arises once we have a valid // might have set. Deal with that as need arises once we have a valid
// use case. // use case.
w.chunking = true w.chunking = true
w.SetHeader("Transfer-Encoding", "chunked") w.header.Set("Transfer-Encoding", "chunked")
} else { } else {
// HTTP version < 1.1: cannot do chunked transfer // HTTP version < 1.1: cannot do chunked transfer
// encoding and we don't know the Content-Length so // encoding and we don't know the Content-Length so
// signal EOF by closing connection. // signal EOF by closing connection.
w.closeAfterReply = true w.closeAfterReply = true
w.chunking = false // redundant w.chunking = false // redundant
w.SetHeader("Transfer-Encoding", "") // in case already set w.header.Del("Transfer-Encoding") // in case already set
} }
if w.req.wantsHttp10KeepAlive() && (w.req.Method == "HEAD" || hasCL) { if w.req.wantsHttp10KeepAlive() && (w.req.Method == "HEAD" || hasCL) {
_, connectionHeaderSet := w.header["Connection"] _, connectionHeaderSet := w.header["Connection"]
if !connectionHeaderSet { if !connectionHeaderSet {
w.SetHeader("Connection", "keep-alive") w.header.Set("Connection", "keep-alive")
} }
} else if !w.req.ProtoAtLeast(1, 1) { } else if !w.req.ProtoAtLeast(1, 1) {
// Client did not ask to keep connection alive. // Client did not ask to keep connection alive.
...@@ -292,7 +281,7 @@ func (w *response) WriteHeader(code int) { ...@@ -292,7 +281,7 @@ func (w *response) WriteHeader(code int) {
// Cannot use Content-Length with non-identity Transfer-Encoding. // Cannot use Content-Length with non-identity Transfer-Encoding.
if w.chunking { if w.chunking {
w.SetHeader("Content-Length", "") w.header.Set("Content-Length", "")
} }
if !w.req.ProtoAtLeast(1, 0) { if !w.req.ProtoAtLeast(1, 0) {
return return
...@@ -307,13 +296,10 @@ func (w *response) WriteHeader(code int) { ...@@ -307,13 +296,10 @@ func (w *response) WriteHeader(code int) {
text = "status code " + codestring text = "status code " + codestring
} }
io.WriteString(w.conn.buf, proto+" "+codestring+" "+text+"\r\n") io.WriteString(w.conn.buf, proto+" "+codestring+" "+text+"\r\n")
for k, v := range w.header { writeSortedHeader(w.conn.buf, w.header, nil)
io.WriteString(w.conn.buf, k+": "+v+"\r\n")
}
io.WriteString(w.conn.buf, "\r\n") io.WriteString(w.conn.buf, "\r\n")
} }
// Write implements the ResponseWriter.Write method
func (w *response) Write(data []byte) (n int, err os.Error) { func (w *response) Write(data []byte) (n int, err os.Error) {
if w.conn.hijacked { if w.conn.hijacked {
log.Print("http: response.Write on hijacked connection") log.Print("http: response.Write on hijacked connection")
...@@ -388,7 +374,7 @@ func errorKludge(w *response) { ...@@ -388,7 +374,7 @@ func errorKludge(w *response) {
msg += " would ignore this error page if this text weren't here.\n" msg += " would ignore this error page if this text weren't here.\n"
// Is it text? ("Content-Type" is always in the map) // Is it text? ("Content-Type" is always in the map)
baseType := strings.Split(w.header["Content-Type"], ";", 2)[0] baseType := strings.Split(w.header.Get("Content-Type"), ";", 2)[0]
switch baseType { switch baseType {
case "text/html": case "text/html":
io.WriteString(w, "<!-- ") io.WriteString(w, "<!-- ")
...@@ -408,8 +394,8 @@ func (w *response) finishRequest() { ...@@ -408,8 +394,8 @@ func (w *response) finishRequest() {
// If this was an HTTP/1.0 request with keep-alive and we sent a Content-Length // If this was an HTTP/1.0 request with keep-alive and we sent a Content-Length
// back, we can make this a keep-alive response ... // back, we can make this a keep-alive response ...
if w.req.wantsHttp10KeepAlive() { if w.req.wantsHttp10KeepAlive() {
_, sentLength := w.header["Content-Length"] sentLength := w.header.Get("Content-Length") != ""
if sentLength && w.header["Connection"] == "keep-alive" { if sentLength && w.header.Get("Connection") == "keep-alive" {
w.closeAfterReply = false w.closeAfterReply = false
} }
} }
...@@ -431,7 +417,6 @@ func (w *response) finishRequest() { ...@@ -431,7 +417,6 @@ func (w *response) finishRequest() {
} }
} }
// Flush implements the ResponseWriter.Flush method.
func (w *response) Flush() { func (w *response) Flush() {
if !w.wroteHeader { if !w.wroteHeader {
w.WriteHeader(StatusOK) w.WriteHeader(StatusOK)
...@@ -504,7 +489,7 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { ...@@ -504,7 +489,7 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
// Error replies to the request with the specified error message and HTTP code. // Error replies to the request with the specified error message and HTTP code.
func Error(w ResponseWriter, error string, code int) { func Error(w ResponseWriter, error string, code int) {
w.SetHeader("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(code) w.WriteHeader(code)
fmt.Fprintln(w, error) fmt.Fprintln(w, error)
} }
...@@ -557,7 +542,7 @@ func Redirect(w ResponseWriter, r *Request, url string, code int) { ...@@ -557,7 +542,7 @@ func Redirect(w ResponseWriter, r *Request, url string, code int) {
} }
} }
w.SetHeader("Location", url) w.Header().Set("Location", url)
w.WriteHeader(code) w.WriteHeader(code)
// RFC2616 recommends that a short note "SHOULD" be included in the // RFC2616 recommends that a short note "SHOULD" be included in the
...@@ -680,7 +665,7 @@ func (mux *ServeMux) match(path string) Handler { ...@@ -680,7 +665,7 @@ func (mux *ServeMux) match(path string) Handler {
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
// Clean path to canonical form and redirect. // Clean path to canonical form and redirect.
if p := cleanPath(r.URL.Path); p != r.URL.Path { if p := cleanPath(r.URL.Path); p != r.URL.Path {
w.SetHeader("Location", p) w.Header().Set("Location", p)
w.WriteHeader(StatusMovedPermanently) w.WriteHeader(StatusMovedPermanently)
return return
} }
...@@ -833,7 +818,7 @@ func ListenAndServe(addr string, handler Handler) os.Error { ...@@ -833,7 +818,7 @@ func ListenAndServe(addr string, handler Handler) os.Error {
// ) // )
// //
// func handler(w http.ResponseWriter, req *http.Request) { // func handler(w http.ResponseWriter, req *http.Request) {
// w.SetHeader("Content-Type", "text/plain") // w.Header().Set("Content-Type", "text/plain")
// w.Write([]byte("This is an example server.\n")) // w.Write([]byte("This is an example server.\n"))
// } // }
// //
......
...@@ -56,7 +56,7 @@ func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) { ...@@ -56,7 +56,7 @@ func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var booleanflag = flag.Bool("boolean", true, "another flag for testing") var booleanflag = flag.Bool("boolean", true, "another flag for testing")
func FlagServer(w http.ResponseWriter, req *http.Request) { func FlagServer(w http.ResponseWriter, req *http.Request) {
w.SetHeader("content-type", "text/plain; charset=utf-8") w.Header.Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprint(w, "Flags:\n") fmt.Fprint(w, "Flags:\n")
flag.VisitAll(func(f *flag.Flag) { flag.VisitAll(func(f *flag.Flag) {
if f.Value.String() != f.DefValue { if f.Value.String() != f.DefValue {
...@@ -93,7 +93,7 @@ func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) { ...@@ -93,7 +93,7 @@ func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// exec a program, redirecting output // exec a program, redirecting output
func DateServer(rw http.ResponseWriter, req *http.Request) { func DateServer(rw http.ResponseWriter, req *http.Request) {
rw.SetHeader("content-type", "text/plain; charset=utf-8") rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
r, w, err := os.Pipe() r, w, err := os.Pipe()
if err != nil { if err != nil {
fmt.Fprintf(rw, "pipe: %s\n", err) fmt.Fprintf(rw, "pipe: %s\n", err)
......
...@@ -509,7 +509,7 @@ var connected = "200 Connected to Go RPC" ...@@ -509,7 +509,7 @@ var connected = "200 Connected to Go RPC"
// ServeHTTP implements an http.Handler that answers RPC requests. // ServeHTTP implements an http.Handler that answers RPC requests.
func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if req.Method != "CONNECT" { if req.Method != "CONNECT" {
w.SetHeader("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed)
io.WriteString(w, "405 must CONNECT\n") io.WriteString(w, "405 must CONNECT\n")
return 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