Go version
go version go1.24.3 darwin/amd64
Output of go env
in your module/workspace:
AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE='auto'
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/tmp/.gocache'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/rittneje/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/kf/kr7_s3xx0l12zbj3jrn082hmzy5gvy/T/go-build2339782732=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='amd64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD=''
GOMODCACHE='/Users/rittneje/go/pkg/mod'
GONOPROXY='[REDACTED]'
GONOSUMDB='[REDACTED]'
GOOS='darwin'
GOPATH='/Users/rittneje/go'
GOPRIVATE='[REDACTED]'
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/rittneje/go1.24.3'
GOSUMDB='sum.golang.org'
GOTELEMETRY='off'
GOTELEMETRYDIR='/Users/rittneje/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/Users/rittneje/go1.24.3/pkg/tool/darwin_amd64'
GOVCS='[REDACTED]'
GOVERSION='go1.24.3'
GOWORK=''
PKG_CONFIG='pkg-config'
What did you do?
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/http/httputil"
"strings"
)
func main() {
s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
b, err := httputil.DumpRequest(req, req.URL.Path == "/dump-body")
if err != nil {
panic(err)
}
fmt.Println("received request: " + string(b))
req.Body.Close()
fmt.Println(io.ReadAll(req.Body))
}))
defer s.Close()
req, err := http.NewRequest(http.MethodGet, s.URL, strings.NewReader("blahblahblah"))
if err != nil {
panic(err)
}
if _, err := s.Client().Do(req); err != nil {
panic(err)
}
fmt.Println("\n----------\n")
req, err = http.NewRequest(http.MethodGet, s.URL+"/dump-body", strings.NewReader("blahblahblah"))
if err != nil {
panic(err)
}
req.Header.Set("Dump", "true")
if _, err := s.Client().Do(req); err != nil {
panic(err)
}
}
What did you see happen?
For the first request, where the body isn't dumped, I see "http: invalid Read on closed Body", which corresponds to http.ErrBodyReadAfterClose
.
For the second request, where the body is dumped, reading the body after closing it worked.
This means that code that works when httputil.DumpRequest
is used may not work when it isn't.
What did you expect to see?
I expected httputil.DumpRequest
to replace the body in such a way that it preserves the semantics described in the net/http documentation. Namely, it should still return http.ErrBodyReadAfterClose
if you try to read the body after closing it.
Comment From: seankhliao
http.ErrBodyReadAfterClose
is an error that may be returned on read after close, but not a guaranteed error, such as with other wrappers. The contract for Body is only that it is an io.ReadCloser
.
Given that httputil.DumpRequest
is only for debugging, I think this should be a won't fix.
Comment From: gabyhelp
Related Issues
- net/http/httputil: DumpRequest duplicates the 'Connection: close' header #54616 (closed)
- net/http/httputil: new `resp.Body` returned by `dump*` is `nocloser` #67950 (closed)
- net/http/httputil: DumpRequestOut has wrong headers on chunked body w/ ContentLength 0 (unknown) #34504 (closed)
- net/http: request.Body.Close can be called more than once #40382 (closed)
- net/http: panic/corruption due to undocumented request body read #66714 (closed)
- net/http: document that ParseForm consumes Request.Body #35620 (closed)
- net/http: request.Body.Read() can be called after client.Do() returns and resp.Body is drained/closed #51907
- net/http: req.Body.Close called multiple times in http1, sometimes not called by the time Do returns on error #26408 (closed)
- net/http/httptest: NewRequest sets body to `{}` instead of nil #20965 (closed)
Related Code Changes
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
Comment From: rittneje
@seankhliao This is a bug that can easily lead to code that only works when debug logging is enabled. If it's not going to be fixed, it should at least be mentioned as a potential hazard in the docs for httputil. And wrappers aren't really relevant to this, since they will end up observing the error anyway when the original body is closed.