Description
What version of Go are you using (go version
)?
$ go version 1.20.4
Does this issue reproduce with the latest release?
What operating system and processor architecture are you using (go env
)?
go env
Output
$ go env set GO111MODULE= set GOARCH=amd64 set GOBIN= set GOCACHE=C:\Users\Pim\AppData\Local\go-build set GOENV=C:\Users\Pim\AppData\Roaming\go\env set GOEXE=.exe set GOEXPERIMENT= set GOFLAGS= set GOHOSTARCH=amd64 set GOHOSTOS=windows set GOINSECURE= set GOMODCACHE=C:\Users\Pim\go\pkg\mod set GONOPROXY= set GONOSUMDB= set GOOS=windows set GOPATH=C:\Users\Pim\go set GOPRIVATE= set GOPROXY=https://proxy.golang.org,direct set GOROOT=C:\Program Files\Go set GOSUMDB=sum.golang.org set GOTMPDIR= set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64 set GOVCS= set GOVERSION=go1.20.4 set GCCGO=gccgo set GOAMD64=v1 set AR=ar set CC=gcc set CXX=g++ set CGO_ENABLED=0 set GOMOD=NUL set GOWORK= set CGO_CFLAGS=-O2 -g set CGO_CPPFLAGS= set CGO_CXXFLAGS=-O2 -g set CGO_FFLAGS=-O2 -g set CGO_LDFLAGS=-O2 -g set PKG_CONFIG=pkg-config set GOGCCFLAGS=-m64 -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=C:\Users\Pim\AppData\Local\Temp\go-build1789935570=/tmp/go-build -gno-record-gcc-switches
What did you do?
I've add the ResponseController as given in this example: https://www.alexedwards.net/blog/how-to-use-the-http-responsecontroller-type.
Only difference is that I'm read the request before creating the ResponseController.
I'm using the golang http server + gorilla multiplexer including a middleware handler.
I have a connection state change listener, which shows connection changes when I use the old flusher technique.
I don't see state changes when I use the ResponseController.
Some logging during test:
2023-10-21T14:00:28.453+0200 INFO ResponseWriter before creating Controller: *http.response &http.response{conn:(*http.conn)(0xc00017b320), req:(*http.Request)(0xc000188600), reqBody:(*http.body)(0xc0 0020e500), cancelCtx:(context.CancelFunc)(0xa66820), wroteHeader:false, wroteContinue:false, wants10KeepAlive:false, wantsClose:false, canWriteContinue:atomic.Bool{_:atomic.noCopy{}, v:0x0}, writeContinueMu :sync.Mutex{state:0, sema:0x0}, w:(*bufio.Writer)(0xc00020e540), cw:http.chunkWriter{res:(*http.response)(0xc0002121c0), header:http.Header(nil), wroteHeader:false, chunking:false}, handlerHeader:http.Heade r{}, calledHeader:false, written:0, contentLength:-1, status:0, closeAfterReply:false, requestBodyLimitHit:false, trailers:[]string(nil), handlerDone:atomic.Bool{_:atomic.noCopy{}, v:0x0}, dateBuf:[29]uint8 {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, clenBuf:[10]uint8{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, statusBuf:[3]uint8{0x0, 0x0, 0x0}, closeNotifyCh:(chan bool)(0xc0001c4380), didCloseNotify:atomic.Bool{_:atomic.noCopy{}, v:0x0}}
2023-10-21T14:00:28.453+0200 INFO Created ResponseController: *http.ResponseController &http.ResponseController{rw:(*http.response)(0xc0002121c0)}
2023-10-21T14:00:28.453+0200 INFO ResponseWriter after creating Controller: *http.response &http.response{conn:(*http.conn)(0xc00017b320), req:(*http.Request)(0xc000188600), reqBody:(*http.body)(0xc00 020e500), cancelCtx:(context.CancelFunc)(0xa66820), wroteHeader:false, wroteContinue:false, wants10KeepAlive:false, wantsClose:false, canWriteContinue:atomic.Bool{_:atomic.noCopy{}, v:0x0}, writeContinueMu: sync.Mutex{state:0, sema:0x0}, w:(*bufio.Writer)(0xc00020e540), cw:http.chunkWriter{res:(*http.response)(0xc0002121c0), header:http.Header(nil), wroteHeader:false, chunking:false}, handlerHeader:http.Header {}, calledHeader:false, written:0, contentLength:-1, status:0, closeAfterReply:false, requestBodyLimitHit:false, trailers:[]string(nil), handlerDone:atomic.Bool{_:atomic.noCopy{}, v:0x0}, dateBuf:[29]uint8{ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, clenBuf:[10]uint8{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, statusBuf:[3]uint8{0x0, 0x0, 0x0}, closeNotifyCh:(chan bool)(0xc0001c4380), didCloseNotify:atomic.Bool{_:atomic.noCopy{}, v:0x0}} 2
What did you expect to see?
I was hoping the whole issue with connections being closed would be solved for my streaming case.
What did you see instead?
Bytes were succesfully written but not received by client (client does work with other streams), no errors what so ever from fmt or the response controller.
As the ResponseController is very silent not giving any clue of what is going wrong, I currently am working to reproduce this issue migrating step-by-step from example to my own setup to isolate the cause.
Question
Not sure if this is a bug or whether there are limitations on using the ResponseController.
It is nice that there is a ResponseController to manipulate the connection.
So far I've seen a limitation documented to create the controller before first write (including headers I presume), are there other limitations which I could not find in the documentation but are suffering from?
For instance should the ResponseController be created before reading the request?
My code is working with the old flush workaround, but then the connection closes after being idle for about 30-60 sec.
If there are, please share.