package api import ( "net/http" "github.com/gin-gonic/gin" ) // responseControllerKey is where captureController stashes the controller in // the gin context for responseController to find. const responseControllerKey = "pansy.responseController" // captureController hands every handler an http.ResponseController that can // actually reach the connection. It MUST be the first middleware on the engine. // // A ResponseController finds the connection's deadline setters by unwrapping // the ResponseWriter it was built from, one layer at a time, until it reaches // one that has them. gin's own writer unwraps cleanly. The logging middleware's // does not: it replaces c.Writer with a type that embeds the gin.ResponseWriter // INTERFACE, which has no Unwrap, so a controller built from c.Writer inside a // handler stops there and every SetReadDeadline/SetWriteDeadline returns // ErrNotSupported. That left the per-frame SSE deadline (#78) and the scan // upload's extensions dead in production while their tests — on a bare engine // with no logging — passed: long agent turns were cut at the server's absolute // 30s WriteTimeout, and the client saw "The connection dropped partway through." // // Building the controller here, ahead of every wrapper, sidesteps the question // of what any later middleware does to the writer. Handlers that extend a // deadline take it from responseController; sse_deadline_test.go runs the // scenario through New so a reorder or a new wrapper fails a test. func captureController() gin.HandlerFunc { return func(c *gin.Context) { c.Set(responseControllerKey, http.NewResponseController(c.Writer)) c.Next() } } // responseController returns the controller captureController stored, or — on // an engine without that middleware, which only tests build — one made from // c.Writer as it stands. func responseController(c *gin.Context) *http.ResponseController { if v, ok := c.Get(responseControllerKey); ok { if rc, ok := v.(*http.ResponseController); ok { return rc } } return http.NewResponseController(c.Writer) }