Source File
recorder.go
Belonging Package
net/http/httptest
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package httptest
import (
)
// ResponseRecorder is an implementation of [http.ResponseWriter] that
// records its mutations for later inspection in tests.
type ResponseRecorder struct {
// Code is the HTTP response code set by WriteHeader.
//
// Note that if a Handler never calls WriteHeader or Write,
// this might end up being 0, rather than the implicit
// http.StatusOK. To get the implicit value, use the Result
// method.
Code int
// HeaderMap contains the headers explicitly set by the Handler.
// It is an internal detail.
//
// Deprecated: HeaderMap exists for historical compatibility
// and should not be used. To access the headers returned by a handler,
// use the Response.Header map as returned by the Result method.
HeaderMap http.Header
// Body is the buffer to which the Handler's Write calls are sent.
// If nil, the Writes are silently discarded.
Body *bytes.Buffer
// Flushed is whether the Handler called Flush.
Flushed bool
result *http.Response // cache of Result's return value
snapHeader http.Header // snapshot of HeaderMap at first Write
wroteHeader bool
}
// NewRecorder returns an initialized [ResponseRecorder].
func () *ResponseRecorder {
return &ResponseRecorder{
HeaderMap: make(http.Header),
Body: new(bytes.Buffer),
Code: 200,
}
}
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
// an explicit DefaultRemoteAddr isn't set on [ResponseRecorder].
const DefaultRemoteAddr = "1.2.3.4"
// Header implements [http.ResponseWriter]. It returns the response
// headers to mutate within a handler. To test the headers that were
// written after a handler completes, use the [ResponseRecorder.Result] method and see
// the returned Response value's Header.
func ( *ResponseRecorder) () http.Header {
:= .HeaderMap
if == nil {
= make(http.Header)
.HeaderMap =
}
return
}
// writeHeader writes a header if it was not written yet and
// detects Content-Type if needed.
//
// bytes or str are the beginning of the response body.
// We pass both to avoid unnecessarily generate garbage
// in rw.WriteString which was created for performance reasons.
// Non-nil bytes win.
func ( *ResponseRecorder) ( []byte, string) {
if .wroteHeader {
return
}
if len() > 512 {
= [:512]
}
:= .Header()
, := ["Content-Type"]
:= .Get("Transfer-Encoding") != ""
if ! && ! {
if == nil {
= []byte()
}
.Set("Content-Type", http.DetectContentType())
}
.WriteHeader(200)
}
// Write implements http.ResponseWriter. The data in buf is written to
// rw.Body, if not nil.
func ( *ResponseRecorder) ( []byte) (int, error) {
.writeHeader(, "")
if .Body != nil {
.Body.Write()
}
return len(), nil
}
// WriteString implements [io.StringWriter]. The data in str is written
// to rw.Body, if not nil.
func ( *ResponseRecorder) ( string) (int, error) {
.writeHeader(nil, )
if .Body != nil {
.Body.WriteString()
}
return len(), nil
}
func checkWriteHeaderCode( int) {
// Issue 22880: require valid WriteHeader status codes.
// For now we only enforce that it's three digits.
// In the future we might block things over 599 (600 and above aren't defined
// at https://httpwg.org/specs/rfc7231.html#status.codes)
// and we might block under 200 (once we have more mature 1xx support).
// But for now any three digits.
//
// We used to send "HTTP/1.1 000 0" on the wire in responses but there's
// no equivalent bogus thing we can realistically send in HTTP/2,
// so we'll consistently panic instead and help people find their bugs
// early. (We can't return an error from WriteHeader even if we wanted to.)
if < 100 || > 999 {
panic(fmt.Sprintf("invalid WriteHeader code %v", ))
}
}
// WriteHeader implements [http.ResponseWriter].
func ( *ResponseRecorder) ( int) {
if .wroteHeader {
return
}
checkWriteHeaderCode()
.Code =
.wroteHeader = true
if .HeaderMap == nil {
.HeaderMap = make(http.Header)
}
.snapHeader = .HeaderMap.Clone()
}
// Flush implements [http.Flusher]. To test whether Flush was
// called, see rw.Flushed.
func ( *ResponseRecorder) () {
if !.wroteHeader {
.WriteHeader(200)
}
.Flushed = true
}
// Result returns the response generated by the handler.
//
// The returned Response will have at least its StatusCode,
// Header, Body, and optionally Trailer populated.
// More fields may be populated in the future, so callers should
// not DeepEqual the result in tests.
//
// The Response.Header is a snapshot of the headers at the time of the
// first write call, or at the time of this call, if the handler never
// did a write.
//
// The Response.Body is guaranteed to be non-nil and Body.Read call is
// guaranteed to not return any error other than [io.EOF].
//
// Result must only be called after the handler has finished running.
func ( *ResponseRecorder) () *http.Response {
if .result != nil {
return .result
}
if .snapHeader == nil {
.snapHeader = .HeaderMap.Clone()
}
:= &http.Response{
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
StatusCode: .Code,
Header: .snapHeader,
}
.result =
if .StatusCode == 0 {
.StatusCode = 200
}
.Status = fmt.Sprintf("%03d %s", .StatusCode, http.StatusText(.StatusCode))
if .Body != nil {
.Body = io.NopCloser(bytes.NewReader(.Body.Bytes()))
} else {
.Body = http.NoBody
}
.ContentLength = parseContentLength(.Header.Get("Content-Length"))
if , := .snapHeader["Trailer"]; {
.Trailer = make(http.Header, len())
for , := range {
for , := range strings.Split(, ",") {
= http.CanonicalHeaderKey(textproto.TrimString())
if !httpguts.ValidTrailerHeader() {
// Ignore since forbidden by RFC 7230, section 4.1.2.
continue
}
, := .HeaderMap[]
if ! {
continue
}
:= make([]string, len())
copy(, )
.Trailer[] =
}
}
}
for , := range .HeaderMap {
if !strings.HasPrefix(, http.TrailerPrefix) {
continue
}
if .Trailer == nil {
.Trailer = make(http.Header)
}
for , := range {
.Trailer.Add(strings.TrimPrefix(, http.TrailerPrefix), )
}
}
return
}
// parseContentLength trims whitespace from s and returns -1 if no value
// is set, or the value if it's >= 0.
//
// This a modified version of same function found in net/http/transfer.go. This
// one just ignores an invalid header.
func parseContentLength( string) int64 {
= textproto.TrimString()
if == "" {
return -1
}
, := strconv.ParseUint(, 10, 63)
if != nil {
return -1
}
return int64()
}
The pages are generated with Golds v0.7.0-preview. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |