mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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
This commit is contained in:
parent
fe8639a9fb
commit
2c420ece67
16 changed files with 107 additions and 110 deletions
|
|
@ -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) {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
}
|
}
|
||||||
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) {
|
||||||
}
|
}
|
||||||
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) {
|
||||||
}
|
}
|
||||||
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) {
|
||||||
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) {
|
||||||
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) {
|
||||||
// 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) {
|
||||||
}
|
}
|
||||||
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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,9 @@ import (
|
||||||
// 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,7 +25,7 @@ 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 {
|
||||||
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() {
|
||||||
// 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) {
|
||||||
// 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 {
|
||||||
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{
|
||||||
"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) {
|
||||||
}
|
}
|
||||||
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) {
|
||||||
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) {
|
||||||
|
|
||||||
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) {
|
||||||
// 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) {
|
||||||
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 {
|
||||||
// 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)
|
||||||
|
|
@ -110,7 +103,7 @@ type response struct {
|
||||||
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
|
||||||
|
|
@ -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) {
|
||||||
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) {
|
||||||
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) {
|
||||||
} 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) {
|
||||||
// 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) {
|
||||||
|
|
||||||
// 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) {
|
||||||
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) {
|
||||||
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() {
|
||||||
// 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() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
|
||||||
// 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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// 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) {
|
||||||
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) {
|
||||||
|
|
||||||
// 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"
|
||||||
// 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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue