]> Cypherpunks repositories - gostls13.git/commitdiff
http: change ResponseWriter.SetHeader(k,v) to Header() accessor
authorBrad Fitzpatrick <bradfitz@golang.org>
Wed, 9 Mar 2011 17:41:01 +0000 (09:41 -0800)
committerBrad Fitzpatrick <bradfitz@golang.org>
Wed, 9 Mar 2011 17:41:01 +0000 (09:41 -0800)
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

16 files changed:
src/cmd/godoc/godoc.go
src/cmd/godoc/main.go
src/pkg/expvar/expvar.go
src/pkg/http/cgi/child.go
src/pkg/http/cgi/host.go
src/pkg/http/cgi/host_test.go
src/pkg/http/cgi/matryoshka_test.go
src/pkg/http/fs.go
src/pkg/http/httptest/recorder.go
src/pkg/http/pprof/pprof.go
src/pkg/http/response.go
src/pkg/http/responsewrite_test.go
src/pkg/http/serve_test.go
src/pkg/http/server.go
src/pkg/http/triv.go
src/pkg/rpc/server.go

index 9dce5edf9491dd1c3292b0e4ddb8fb0d874a5c58..41bd37ad665044e88fc3995cab3aab25e6c7179c 100644 (file)
@@ -702,7 +702,7 @@ func servePage(w http.ResponseWriter, title, subtitle, query string, content []b
 
 
 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)
 }
 
index 1ebb802790ffe7c7bc4220996309104c85a8a5ec..f32a5b91459052f395cf6b187ff420076b7a19c0 100644 (file)
@@ -111,7 +111,7 @@ func exec(rw http.ResponseWriter, args []string) (status int) {
                os.Stderr.Write(buf.Bytes())
        }
        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())
        }
 
index b1f0f6c1b81edd332acc24ae2dad371fe8fe610d..ed6cff78db40fbf9eda4ae796f9de1c279977aa7 100644 (file)
@@ -269,7 +269,7 @@ func Iter() <-chan KeyValue {
 }
 
 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")
        first := true
        for name, value := range vars {
index 50f90e526303aea603380841246e4f4f41af1b96..e410c0aa23abbcbf7dab9fee446f2f407b5de071 100644 (file)
@@ -149,12 +149,8 @@ func (r *response) RemoteAddr() string {
        return os.Getenv("REMOTE_ADDR")
 }
 
-func (r *response) SetHeader(k, v string) {
-       if v == "" {
-               r.header.Del(k)
-       } else {
-               r.header.Set(k, v)
-       }
+func (r *response) Header() http.Header {
+       return r.header
 }
 
 func (r *response) Write(p []byte) (n int, err os.Error) {
index 4a2efc781823d81e15254d99378c42ccf7daaa22..d6c8ab22a11f9e91fafc8d2feebdcf86dde7e1d7 100644 (file)
@@ -139,7 +139,7 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
        }
 
        linebody := line.NewReader(cmd.Stdout, 1024)
-       headers := make(map[string]string)
+       headers := rw.Header()
        statusCode := http.StatusOK
        for {
                line, isPrefix, err := linebody.ReadLine()
@@ -181,12 +181,9 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
                        }
                        statusCode = code
                default:
-                       headers[header] = val
+                       headers.Add(header, val)
                }
        }
-       for h, v := range headers {
-               rw.SetHeader(h, v)
-       }
        rw.WriteHeader(statusCode)
 
        _, err = io.Copy(rw, linebody)
index 3362ae5805927e4fd518b9e1f515603376adaee2..2db08d5429830cee9731dbcbfc80c7f3e016ab05 100644 (file)
@@ -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)
 
-       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)
        }
-       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)
        }
 }
index 4bf9c19cb76b6325d6ef8d4c6fa3801c3e09a2e9..3e4a6addfa5dd182ff57bac33ed4cc0632954650 100644 (file)
@@ -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)
 
-       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)
        }
-       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)
        }
 }
@@ -58,7 +58,7 @@ func TestBeChildCGIProcess(t *testing.T) {
                return
        }
        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")
                req.ParseForm()
                for k, vv := range req.Form {
index a4cd7072e1211bf36a947b29d87fa5aadf00cfee..4ad680ccc314bdbd9948ca396f25deace3d4c3bc 100644 (file)
@@ -108,7 +108,7 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
                w.WriteHeader(StatusNotModified)
                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
        if d.IsDirectory() {
@@ -137,16 +137,16 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
        // use extension to find content type.
        ext := filepath.Ext(name)
        if ctype := mime.TypeByExtension(ext); ctype != "" {
-               w.SetHeader("Content-Type", ctype)
+               w.Header().Set("Content-Type", ctype)
        } else {
                // read first chunk to decide between utf-8 text and binary
                var buf [1024]byte
                n, _ := io.ReadFull(f, buf[:])
                b := buf[:n]
                if isText(b) {
-                       w.SetHeader("Content-Type", "text-plain; charset=utf-8")
+                       w.Header().Set("Content-Type", "text-plain; charset=utf-8")
                } 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
        }
@@ -166,11 +166,11 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
                }
                size = ra.length
                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.SetHeader("Content-Length", strconv.Itoa64(size))
+       w.Header().Set("Accept-Ranges", "bytes")
+       w.Header().Set("Content-Length", strconv.Itoa64(size))
 
        w.WriteHeader(code)
 
index ec7bde8aae281ae4c60d89d52e1e146e5b41087c..22827a31db1b42f8f5cbdacc07dc77fc4cf9623c 100644 (file)
@@ -14,11 +14,10 @@ import (
 // ResponseRecorder is an implementation of http.ResponseWriter that
 // records its mutations for later inspection in tests.
 type ResponseRecorder struct {
-       Code    int           // the HTTP response code from WriteHeader
-       Header  http.Header   // if non-nil, the headers to populate
-       Body    *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
-       Flushed bool
-
+       Code           int           // the HTTP response code from WriteHeader
+       HeaderMap      http.Header   // the HTTP response headers
+       Body           *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
+       Flushed        bool
        FakeRemoteAddr string // the fake RemoteAddr to return, or "" for DefaultRemoteAddr
        FakeUsingTLS   bool   // whether to return true from the UsingTLS method
 }
@@ -26,8 +25,8 @@ type ResponseRecorder struct {
 // NewRecorder returns an initialized ResponseRecorder.
 func NewRecorder() *ResponseRecorder {
        return &ResponseRecorder{
-               Header: http.Header(make(map[string][]string)),
-               Body:   new(bytes.Buffer),
+               HeaderMap: make(http.Header),
+               Body:      new(bytes.Buffer),
        }
 }
 
@@ -49,15 +48,9 @@ func (rw *ResponseRecorder) UsingTLS() bool {
        return rw.FakeUsingTLS
 }
 
-// SetHeader populates rw.Header, if non-nil.
-func (rw *ResponseRecorder) SetHeader(k, v string) {
-       if rw.Header != nil {
-               if v == "" {
-                       rw.Header.Del(k)
-               } else {
-                       rw.Header.Set(k, v)
-               }
-       }
+// Header returns the response headers.
+func (rw *ResponseRecorder) Header() http.Header {
+       return rw.HeaderMap
 }
 
 // Write always succeeds and writes to rw.Body, if not nil.
index f7db9aab93bf220d916c83e27f943a63fa1ef5fc..0bac26687d73d842b99793d5b75c142d8ee4a053 100644 (file)
@@ -41,14 +41,14 @@ func init() {
 // command line, with arguments separated by NUL bytes.
 // The package initialization registers it as /debug/pprof/cmdline.
 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"))
 }
 
 // Heap responds with the pprof-formatted heap profile.
 // The package initialization registers it as /debug/pprof/heap.
 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)
 }
 
@@ -56,7 +56,7 @@ func Heap(w http.ResponseWriter, r *http.Request) {
 // responding with a table mapping program counters to function names.
 // The package initialization registers it as /debug/pprof/symbol.
 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
        // do have symbol information.  Pprof only cares whether
index 3d77c55551081b7495a579187ed428baefe2247c..7ac7fb81f3500e9a88b13bd85932eda555a0ad3e 100644 (file)
@@ -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 {
        keys := make([]string, 0, len(h))
        for k := range h {
-               if !exclude[k] {
+               if exclude == nil || !exclude[k] {
                        keys = append(keys, k)
                }
        }
        sort.SortStrings(keys)
        for _, k := range keys {
                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 {
                                return err
                        }
index 228ed5f7d1ed6bc94addceb61f1add6ce8f8b412..0ef7f041e94e9285be432622c88c6571e1401900 100644 (file)
@@ -65,6 +65,29 @@ var respWriteTests = []respWriteTest{
                        "Transfer-Encoding: chunked\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) {
@@ -78,7 +101,7 @@ func TestResponseWrite(t *testing.T) {
                }
                sraw := braw.String()
                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
                }
        }
index 40ad68151e1d4b47d57770eb09f58fe8cf6516ec..a6d3cab09d46a581bbb7c302a94bbec7ccbf7db6 100644 (file)
@@ -144,7 +144,7 @@ func TestConsumingBodyOnNextConn(t *testing.T) {
 type stringHandler string
 
 func (s stringHandler) ServeHTTP(w ResponseWriter, r *Request) {
-       w.SetHeader("Result", string(s))
+       w.Header().Set("Result", string(s))
 }
 
 var handlers = []struct {
@@ -216,7 +216,7 @@ func TestMuxRedirectLeadingSlashes(t *testing.T) {
 
                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)
                        return
                }
@@ -294,8 +294,8 @@ func TestServerTimeouts(t *testing.T) {
 // TestIdentityResponse verifies that a handler can unset 
 func TestIdentityResponse(t *testing.T) {
        handler := HandlerFunc(func(rw ResponseWriter, req *Request) {
-               rw.SetHeader("Content-Length", "3")
-               rw.SetHeader("Transfer-Encoding", req.FormValue("te"))
+               rw.Header().Set("Content-Length", "3")
+               rw.Header().Set("Transfer-Encoding", req.FormValue("te"))
                switch {
                case req.FormValue("overwrite") == "1":
                        _, err := rw.Write([]byte("foo TOO LONG"))
@@ -303,7 +303,7 @@ func TestIdentityResponse(t *testing.T) {
                                t.Errorf("expected ErrContentLength; got %v", err)
                        }
                case req.FormValue("underwrite") == "1":
-                       rw.SetHeader("Content-Length", "500")
+                       rw.Header().Set("Content-Length", "500")
                        rw.Write([]byte("too short"))
                default:
                        rw.Write([]byte("foo"))
index c48aa8d67db382674f54864607fe107ba66747b0..8a8cdd93328f07fe188774a47da46aa02b7b0a0d 100644 (file)
@@ -54,17 +54,10 @@ type ResponseWriter interface {
        // UsingTLS returns true if the client is connected using TLS
        UsingTLS() bool
 
-       // SetHeader sets a header line in the eventual response.
-       // For example, SetHeader("Content-Type", "text/html; charset=utf-8")
-       // will result in the header line
-       //
-       //      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)
+       // Header returns the header map that will be sent by WriteHeader.
+       // Changing the header after a call to WriteHeader (or Write) has
+       // no effect.
+       Header() Header
 
        // 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)
@@ -106,14 +99,14 @@ type conn struct {
 // A response represents the server side of an HTTP response.
 type response struct {
        conn          *conn
-       req           *Request          // request for this response
-       chunking      bool              // using chunked transfer encoding for reply body
-       wroteHeader   bool              // reply header has been written
-       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
+       req           *Request // request for this response
+       chunking      bool     // using chunked transfer encoding for reply body
+       wroteHeader   bool     // reply header has been written
+       wroteContinue bool     // 100 Continue response was written
+       header        Header   // 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
        // updated after response from handler if there's a
@@ -174,7 +167,7 @@ func (c *conn) readRequest() (w *response, err os.Error) {
        w = new(response)
        w.conn = c
        w.req = req
-       w.header = make(map[string]string)
+       w.header = make(Header)
        w.contentLength = -1
 
        // Expect 100 Continue support
@@ -185,21 +178,16 @@ func (c *conn) readRequest() (w *response, err os.Error) {
        return w, nil
 }
 
-// UsingTLS implements the ResponseWriter.UsingTLS
 func (w *response) UsingTLS() bool {
        return w.conn.usingTLS
 }
 
-// RemoteAddr implements the ResponseWriter.RemoteAddr method
 func (w *response) RemoteAddr() string { return w.conn.remoteAddr }
 
-// SetHeader implements the ResponseWriter.SetHeader method
-// An empty value removes the header from the map.
-func (w *response) SetHeader(hdr, val string) {
-       w.header[CanonicalHeaderKey(hdr)] = val, val != ""
+func (w *response) Header() Header {
+       return w.header
 }
 
-// WriteHeader implements the ResponseWriter.WriteHeader method
 func (w *response) WriteHeader(code int) {
        if w.conn.hijacked {
                log.Print("http: response.WriteHeader on hijacked connection")
@@ -214,46 +202,47 @@ func (w *response) WriteHeader(code int) {
        if code == StatusNotModified {
                // Must not have body.
                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
                                // 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
+                               w.header.Del(header)
                        }
                }
        } 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.Get("Content-Type") == "" {
+                       w.header.Set("Content-Type", "text/html; charset=utf-8")
                }
        }
 
-       if w.header["Date"] == "" {
-               w.SetHeader("Date", time.UTC().Format(TimeFormat))
+       if w.header.Get("Date") == "" {
+               w.Header().Set("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 {
+       if clenStr := w.header.Get("Content-Length"); clenStr != "" {
                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", "")
+                       w.header.Set("Content-Length", "")
                }
        }
 
-       te, hasTE := w.header["Transfer-Encoding"]
+       te := w.header.Get("Transfer-Encoding")
+       hasTE := te != ""
        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", "")
+               w.header.Set("Content-Length", "")
                hasCL = false
        }
 
@@ -262,7 +251,7 @@ func (w *response) WriteHeader(code int) {
        } else if hasCL {
                w.chunking = false
                w.contentLength = contentLength
-               w.SetHeader("Transfer-Encoding", "")
+               w.header.Del("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.
@@ -270,20 +259,20 @@ func (w *response) WriteHeader(code int) {
                // might have set.  Deal with that as need arises once we have a valid
                // use case.
                w.chunking = true
-               w.SetHeader("Transfer-Encoding", "chunked")
+               w.header.Set("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
+               w.chunking = false                // redundant
+               w.header.Del("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")
+                       w.header.Set("Connection", "keep-alive")
                }
        } else if !w.req.ProtoAtLeast(1, 1) {
                // Client did not ask to keep connection alive.
@@ -292,7 +281,7 @@ func (w *response) WriteHeader(code int) {
 
        // Cannot use Content-Length with non-identity Transfer-Encoding.
        if w.chunking {
-               w.SetHeader("Content-Length", "")
+               w.header.Set("Content-Length", "")
        }
        if !w.req.ProtoAtLeast(1, 0) {
                return
@@ -307,13 +296,10 @@ func (w *response) WriteHeader(code int) {
                text = "status code " + codestring
        }
        io.WriteString(w.conn.buf, proto+" "+codestring+" "+text+"\r\n")
-       for k, v := range w.header {
-               io.WriteString(w.conn.buf, k+": "+v+"\r\n")
-       }
+       writeSortedHeader(w.conn.buf, w.header, nil)
        io.WriteString(w.conn.buf, "\r\n")
 }
 
-// Write implements the ResponseWriter.Write method
 func (w *response) Write(data []byte) (n int, err os.Error) {
        if w.conn.hijacked {
                log.Print("http: response.Write on hijacked connection")
@@ -388,7 +374,7 @@ func errorKludge(w *response) {
        msg += " would ignore this error page if this text weren't here.\n"
 
        // 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 {
        case "text/html":
                io.WriteString(w, "<!-- ")
@@ -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
        // back, we can make this a keep-alive response ...
        if w.req.wantsHttp10KeepAlive() {
-               _, sentLength := w.header["Content-Length"]
-               if sentLength && w.header["Connection"] == "keep-alive" {
+               sentLength := w.header.Get("Content-Length") != ""
+               if sentLength && w.header.Get("Connection") == "keep-alive" {
                        w.closeAfterReply = false
                }
        }
@@ -431,7 +417,6 @@ func (w *response) finishRequest() {
        }
 }
 
-// Flush implements the ResponseWriter.Flush method.
 func (w *response) Flush() {
        if !w.wroteHeader {
                w.WriteHeader(StatusOK)
@@ -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.
 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)
        fmt.Fprintln(w, error)
 }
@@ -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)
 
        // RFC2616 recommends that a short note "SHOULD" be included in the
@@ -680,7 +665,7 @@ func (mux *ServeMux) match(path string) Handler {
 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.Header().Set("Location", p)
                w.WriteHeader(StatusMovedPermanently)
                return
        }
@@ -833,7 +818,7 @@ func ListenAndServe(addr string, handler Handler) os.Error {
 //     )
 //
 //     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"))
 //     }
 //
index 52d521d3db3a3f444251d4cc9a627a504d9fa6c9..47257e3c230edb807c106c4f6bb4b995c357e531 100644 (file)
@@ -56,7 +56,7 @@ func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 var booleanflag = flag.Bool("boolean", true, "another flag for testing")
 
 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")
        flag.VisitAll(func(f *flag.Flag) {
                if f.Value.String() != f.DefValue {
@@ -93,7 +93,7 @@ func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 
 // exec a program, redirecting output
 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()
        if err != nil {
                fmt.Fprintf(rw, "pipe: %s\n", err)
index aa51f452f255d263bbbd9862d26493d3a0da93b3..90ee25343c044bab2852eb05c7c0720f5ad4cb6f 100644 (file)
@@ -509,7 +509,7 @@ var connected = "200 Connected to Go RPC"
 // ServeHTTP implements an http.Handler that answers RPC requests.
 func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
        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)
                io.WriteString(w, "405 must CONNECT\n")
                return