I create a timeout middleware and I keep getting a warning that says 'Headers were already written. Wanted to override status code 504 with 500' whenever it times out. I want to avoid that warning by modifying the timeout to return whatever result the handlers produce, like this:
func Timeout(timeout time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), timeout)
defer cancel()
c.Request = c.Request.WithContext(ctx)
resChan := make(chan ResponseData)
go func() {
c.Set(ResChan, resChan)
c.Next()
close(resChan)
}()
select {
case <-resChan:
res := <-resChan
if res.Error != nil && res.Error != context.DeadlineExceeded {
c.JSON(res.StatusCode, response.ErrorResponse(res.Error))
return
} else {
c.JSON(res.StatusCode, response.SuccessResponse(res.Message, res.Data))
}
case <-ctx.Done():
if ctx.Err() == context.DeadlineExceeded {
err := errors.New("Service is unavailable or timed out")
c.JSON(http.StatusGatewayTimeout, response.ErrorResponse(err))
c.Abort()
return
}
}
}
}
I've obtained the channel from the Gin context and I'm using it to send the response, but the channel never receives any data that I send. Where did I go wrong?
func (h *AuthHandler) Register(ctx *gin.Context) {
reqCtx := ctx.Request.Context()
resChan := ctx.MustGet(middleware.ResChan)
var req request.RegisterRequest
if err := ctx.ShouldBind(&req); err != nil {
ctx.JSON(http.StatusBadRequest, response.ErrorResponse(err))
return
}
arg := entity.Admin{
Name: req.Name,
Email: req.Email,
Password: req.Password,
}
result, err := h.authService.Register(reqCtx, arg)
if err != nil {
resChan.(chan middleware.ResponseData) <- middleware.ResponseData{
StatusCode: http.StatusInternalServerError,
Error: err,
}
} else {
resChan.(chan middleware.ResponseData) <- middleware.ResponseData{
StatusCode: http.StatusCreated,
Message: "Registration completed successfully.",
Data: response.NewAdminResponse(result),
}
}
}
In your Timeout
handler you read twice from the resChan
:
select {
case <-resChan:
res := <-resChan
but you discard the first value received.
The second receive succeeds as the chan gets closed when c.Next()
returns and thus you'll get the default value.
What you want is
select {
case res := <-resChan:
if res.Error != nil ... {