8

I am trying to add SSE(Server sent events) in NodeJs, But when I am sending response using res.write() the data is not getting sent, but only after writing res.end() all data is being sent at the same time.

I have already found many posts on Github, StackOverflow, regarding this issue and everywhere it is mentioned to use res.flush() after every res.write() but that too isn't working for me, also I am not using any compression module explicitly.

Server-Side Code

Can anyone please tell me is there any way I can make this work.

const express = require('express')

const app = express()
app.use(express.static('public'))

app.get('/countdown', function(req, res) {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  })
  countdown(res, 10)
})

function countdown(res, count) {
  res.write("data: " + count + "\n\n")
  if (count)
    setTimeout(() => countdown(res, count-1), 1000)
  else
    res.end()
}

app.listen(3000, () => console.log('SSE app listening on port 3000!'))

Client-Side Code

<html>
<head>
<script>
  if (!!window.EventSource) {
    var source = new EventSource('/countdown')

    source.addEventListener('message', function(e) {
      document.getElementById('data').innerHTML = e.data
    }, false)

    source.addEventListener('open', function(e) {
      document.getElementById('state').innerHTML = "Connected"
    }, false)

    source.addEventListener('error', function(e) {
      const id_state = document.getElementById('state')
      if (e.eventPhase == EventSource.CLOSED)
        source.close()
      if (e.target.readyState == EventSource.CLOSED) {
        id_state.innerHTML = "Disconnected"
      }
      else if (e.target.readyState == EventSource.CONNECTING) {
        id_state.innerHTML = "Connecting..."
      }
    }, false)
  } else {
    console.log("Your browser doesn't support SSE")
  }
</script>
</head>
<body>
  <h1>SSE: <span id="state"></span></h1>
  <h3>Data: <span id="data"></span></h3>
</body>
</html>

Solution - I was using nginx for reverse proxy that's why it was happening so I tried this solution and it worked :)

EventSource / Server-Sent Events through Nginx

Pier-Luc Gendreau
  • 13,553
  • 4
  • 58
  • 69
Sudhanshu Gaur
  • 7,486
  • 9
  • 47
  • 94

3 Answers3

7

If your Express server is behind a firewall or proxy server, they will often wait until the server closes the connection before sending the entire response. Instead, you need to have a connection between the browser and the server that allows the 'Connection': 'keep-alive'.

Brent Washburne
  • 12,904
  • 4
  • 60
  • 82
  • 2
    It is working man (y), damn why didn't I think about it, thanks to a ton man really thanks a lot. Finally, after 3 days I got the solution Cheers :) – Sudhanshu Gaur Apr 25 '20 at 20:14
2

In my case, the problem was a compression middleware. It worked fine again after after removing the compression middleware for any SSE requests.

Hinrich
  • 13,485
  • 7
  • 43
  • 66
-3

In your example, to prevent from finishing whole iteration to send the events, you need to add res.end() right after res.write(). This way each event is sent after 1 sec.

function countdown(res, count) {
  res.write("data: " + count + "\n\n")
  res.end() // <-- add after each event
  if (count)
    setTimeout(() => countdown(res, count-1), 1000)
  else
    res.end()
}
  • I'm getting `Error [ERR_STREAM_WRITE_AFTER_END]: write after end` though – kyw Oct 03 '21 at 00:56
  • 1
    ah just adding this works: `res.writeHead(200, {'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive'})`. Now I'm seeing all my `res.write()` output in my Fetch. – kyw Oct 03 '21 at 00:58
  • 1
    You can `end()` connection only once and it should be done at the very end. – Bartosz Rosa Dec 30 '22 at 16:55