Search code examples
gogo-gin

Problem with Sending Data through Channel with Gin Context


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),
        }
    }
}

Solution

  • 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 ... {