goconcurrencygoroutinego-gingo-http

How to solve "Headers were already written. Wanted to override status code 200 with 400" In gin


I'm trying to calculate a report independently of an HTTP request using the Gin framework, but I also want to return it to the request if it hasn't been closed yet. The following code snippet sometimes works correctly, but sometimes throws the error "Headers were already written. Wanted to override status code 200 with 400". I couldn't find a solution.

// router.go
reportRoute.POST("/query", func(c *gin.Context) {
        reporter.ReportInChan <- c
    }) // Unpublish report by id
//reporter
package reporter

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

var (
    ReportInChan = make(chan *gin.Context)
)

func Listener() {
    for {
        select {
        case req := <-ReportInChan:
            queryReports(req)
        }
    }
}

func queryReports(req *gin.Context) {
    type ReqQuery struct {
        QueryID    uint           `json:"query_id"`
        Parameters map[string]any `json:"parameters"`
    }
    type Req struct {
        ReportID uint       `json:"report_id"`
        Queries  []ReqQuery `json:"queries"`
    }

    var reqBody Req
    err := req.BindJSON(&reqBody)
    if err != nil {
        fmt.Println("hata var")
        if ctx := req.Request.Context(); ctx.Done() == nil {
            req.JSON(400, gin.H{"error": "Veriler Yanlış Gönderiliyor. Lütfen Bilgi İşlem Birimiyle İletişime Geçin ", "data": nil})
        }
        return
    }
}

Solution

  • This handler below will send the context to the channel, and then return. Once the handler returns, the request will be canceled. At this point, the receiving goroutine may not have had a chance of producing any output at all, but sometimes, it may. If the receiving goroutine starts producing output right at the point after the Gin framework writes the response but before it cancels the context, you'll get the mentioned error.

    reportRoute.POST("/query", func(c *gin.Context) {
            reporter.ReportInChan <- c
    }) 
    

    What you really need to do is implement a timeout scheme, so if the request handler can respond before that timeout, you write it to the output and return. Otherwise, you let the request handler run, and find another way to return the results to the caller, maybe through another API call:

    type reportRequest struct {
        ReportParams
        result chan ReportResult
    }
    
    reportRoute.POST("/query", func(c *gin.Context) {
            req:=reportRequest {
                  ReportParams: {...},
                  result: make(chan ReportResult),
            }
            reporter.ReportInChan <- req
            select {
               case time.After(100*time.Millisecond): 
                   // Wait 100 msecs 
               case result<-req.result:
                   // Write the result
            }
    })
    

    The above code will create a report request from the request context, create a return channel, and then send it to the report processor. If the report processor responds within 100 msecs, the results can be written. Otherwise, the results should be stored somewhere else that can be retrieved by another API.

    The report processor should look like:

    func (r Reporter) reportProcessor() {
       for request:=range r.ReportInChan {
           result:=processReport(request)
           select {
              case request.result <- result:
                  // Wrote the result
              default:
                   // Cannot write the result, handler returned
                 storeResult(result)
       }
    }