fix: restore original writer so unmatched routes still return 404 - #88
c879873067877881111 wants to merge 3 commits into
Conversation
When the middleware is registered globally via Use(), gin runs it inside serveError(), which sets the status on c.writermem and then writes the default body through c.Writer after the middleware returns. The finish branch freed the buffer but left c.Writer pointing at the timeout Writer, so that write landed in a nil body and was silently dropped. The status was never flushed either, so net/http sent an implicit 200 with an empty body while gin's logger reported 404. Restore c.Writer to the original writer once the buffer is flushed, the same way the panic branch already does. This also fixes the 405 response for HandleMethodNotAllowed, which goes through the same path.
|
I ran into a related issue while using Gin's access logger with this middleware. The response was sent correctly, but BodySize was always logged as -1 for routes using the timeout middleware. FreeBuffer resets the timeout Writer's size to -1, while c.Writer still points to that Writer when the outer logger runs. This PR fixes the normal completion path by restoring the original Writer, but the same issue remains in the actual timeout path. Restoring c.Writer after the handler goroutine finishes fixes BodySize for timeout responses as well. |
FreeBuffer() sets the timeout Writer's size to -1, and the timer branch left c.Writer pointing at it, so middleware that inspects c.Writer after c.Next() read -1 instead of the size of the response actually written. gin's own logger reports that as BodySize. Restore the writer next to c.Abort(), after the wait on the handler goroutine, since that is the point where touching c is safe. Reported by @zHElEARN.
|
You're right about the timeout path, and I put the restore next to One thing worth separating out, since it looks like the same bug but isn't. Measuring with an outer middleware in the same position as
The no-route Added |
Closes #87
The bug
With the middleware registered globally via
Use(), any request that doesn't match a route gets200 OKwith an empty body instead of gin's404 page not found. Gin's own logger still prints 404, so the log and the wire disagree.Why
Gin handles a no-route request in
serveError():https://github.com/gin-gonic/gin/blob/v1.12.0/gin.go#L764-L779
The status goes on
c.writermembefore the middleware runs, and the body is written throughc.Writerafter it returns. Thecase <-finish:branch calledFreeBuffer()(settingbody = nil) but leftc.Writerpointing at the timeoutWriter, so:Writehit thew.body == nilguard and returned(0, nil)— the 404 body was silently droppedtw.codewas 0, so no status was flushed either, andnet/httpsent an implicit 200Content-Type: text/plainin the response comes fromc.writermem.Header(), which is why the empty reply still looks like a text responseRegistering per-route (as in
_example/example01) doesn't hit this, because route handlers never run for unmatched routes — which is why the examples never caught it.The fix
Restore
c.Writerto the original writer once the buffer is flushed, the same way the panic branch on line 88 already does.HandleMethodNotAllowedgoes through the sameserveErrorpath, so the 405 response was broken in exactly the same way and is fixed by the same line.Before / after
Same test, only
timeout.gostashed:200 ""404 "404 page not found"200 ""405 "405 method not allowed"200 "world"NoRoutehandler404 {"err":"nope"}408 "Request Timeout"Custom
NoRoutehandlers were never affected — they write throughc.Writerwhile the buffer is still live, so the flush picks them up.Added
TestNoRouteWithUseandTestMethodNotAllowedWithUse.go test -race ./...andgo vet ./...pass.