Как вернуться из обработчика http, когда запрос отменен или истекло время ожидания

Я делаю sse, важный код:

   var clientes=new(sync.Map)
    type canalesStruct struct{
        sender chan []byte
        close chan bool
    }
    func (broker *brokerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
flusher, ok := w.(http.Flusher)
    if !ok {
        http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")
    var ID string
    //Get the ID somehow
    canales:=new(canalesStruct)
    canales.sender=make(chan []byte)
    canales.close=make(chan bool)
    clientes.store(ID,canales)
    notify := w.(http.CloseNotifier).CloseNotify()
    defer func() {
        clientes.Delete(ID)
    }()
    for {
         select {
            case <-notify:
                return
            case <-canales.close:
                return  
            case data:= <-canales.sender:
                fmt.Fprintf(w, "data: %s\n\n",data)
                flusher.Flush()
            }
      }
}

    func sendDataToChanelID(ID string,data []byte){
        canalesRaw,_:=clientes.Load(ID)
        canales,_:=canalRaw(*canalesStruct)
        canales.sender <-data
    }

Итак, у меня есть два вопроса:

  1. Если соединение обрывается ВО ВРЕМЯ получения данных, будет ли fmt.Fprintf продолжать ждать бесконечно или оно вернется немедленно?
  2. Если он возвращается немедленно, проблем нет, но если он продолжает ждать, как я могу обернуть «fmt.Fprintf», чтобы вернуться, если превышено время ожидания?

person John Balvin Arias    schedule 12.07.2018    source источник
comment
Возможный дубликат Закрыть все goroutines при отмене HTTP-запроса   -  person Flimzy    schedule 12.07.2018
comment
Краткий ответ: когда r.Context() отменяется, вам необходимо прервать обработку вашего запроса.   -  person Flimzy    schedule 12.07.2018


Ответы (2)


Я считаю, что вызов fmt.Fprintf() просто завершится ошибкой, когда основной HTTP-запрос будет закрыт.

«Ошибка» здесь означает, что он вернет ненулевую ошибку.

Итак, чтобы правильно обработать случай закрытия HTTP-запроса, проверьте возврат ошибки fmt.Fprintf(), как в

_, err := fmt.Fprintf(w, ...)
if err != nil {
   // We have failed to write to the underlying connection
}
person kostix    schedule 12.07.2018

Простой способ вернуть разные значения в зависимости от времени — это подождать на канале.

func F() int {
    // channel to receive info
    c := make(chan int)
    // start timeout goroutine
    go func() {
        time.Sleep(TIMEOUT)
        c <- -1
    }()
    // start work goroutine
    go func() {
        c <- GetValue()
    }()
    // receive value
    x := <-c
    // start goroutine to discard late value
    go func() {
        _ = <-c
    }()
    // return received value
    return x
}

Итак, две горутины соревнуются друг с другом. Если тайм-аут наступает первым, значение равно -1.

person Nick Hunter    schedule 12.07.2018
comment
Хотя этот подход может работать в целом, это неправильный подход для обработчика http. - person Flimzy; 12.07.2018
comment
Причина, по которой вы это говорите, в том, что в исходной версии я не разрешил должным образом отправить поздней горутине, что привело к утечке мемов? Потому что теперь это исправлено, хех. В противном случае, пожалуйста, объясните, почему, было бы неправильно просто сказать, что это неправильный подход без объяснения причин. - person Nick Hunter; 12.07.2018
comment
Нет, я говорю это, потому что это не учитывает отмену запроса, о чем на самом деле и идет речь. Вам нужно использовать r.Context() для правильного решения. - person Flimzy; 12.07.2018
comment
Ах, справедливо! - person Nick Hunter; 12.07.2018