1

I'm doing sse, the important code is:

   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
    }

So I have two question over there:

  1. If connection drops WHILE is being receiving data, will fmt.Fprintf continue waiting endless or it will return immediately?
  2. In case it returns immediately there is not problem, but in case it continues waiting how can I wrapp "fmt.Fprintf" in order to return if timeout exceed?
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
John Balvin Arias
  • 2,632
  • 3
  • 26
  • 41
  • 2
    Possible duplicate of [Close all goroutines when HTTP request is cancelled](https://stackoverflow.com/questions/45525332/close-all-goroutines-when-http-request-is-cancelled) – Jonathan Hall Jul 12 '18 at 10:51
  • 1
    Short answer: When `r.Context()` is cancelled, you need to abort your request processing. – Jonathan Hall Jul 12 '18 at 11:57

2 Answers2

0

A simple way to return different values depending on timing is to wait on a channel.

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
}

So, the two goroutines are racing each other. If the timeout gets there first, the value is -1.

  • 3
    While this approach may work generally, it is the wrong approach for an http handler. – Jonathan Hall Jul 12 '18 at 12:12
  • Is the reason you say this because I had not, in the original version, properly allowed the "late" goroutine to send, thus leaving a memleak? Because that's fixed now, heh. Otherwise, please explain why, it wouldn't be correct to just say "this is the wrong approach without any reason" – Nick Hunter Jul 12 '18 at 14:36
  • 1
    No, I say it because it doesn't account for request cancellation, which is what the question is really about. You need to use `r.Context()` for a proper solution. – Jonathan Hall Jul 12 '18 at 14:40
  • Ah, fair enough! – Nick Hunter Jul 12 '18 at 14:49
0

I beleive a call to fmt.Fprintf() will just fail when the underlying HTTP request closes.

"To fail" here means it will return a non-nil error.

So, to properly handle the case of HTTP request being shut down, check the error return of fmt.Fprintf(), like in

_, err := fmt.Fprintf(w, ...)
if err != nil {
   // We have failed to write to the underlying connection
}
kostix
  • 51,517
  • 14
  • 93
  • 176