SSE (Server-Sent Events) Using A POST Request Without EventSource
SSE (Server-Sent Events) Using A POST Request Without EventSource
This article will explain how to receive SSE from your
frontend using a HTTP POST request, which is not
supported by
EventSource
. Most articles and documentation will tell you that a
GET
request is required to receive the events, but this is
not true. Most
HTTP
libraries will hide the lower level steps used to
receive a response from a server like
axios
.
We will need a library that allows us to contruct our
own response from each network request received, AKA
each SSE message.
How will this work?
-
First we will use
fetch
to make thePOST
request to our SSE endpoint. This endpoint will be setup to return multiple messages with a response headerContentType: text/event-stream
.
const response = await fetch('/sse', {
method: 'POST',
headers: {
'Content-Type': 'text/event-stream'
},
body: {
"user_id": 123
}
})
2. Create a
reader
instance to get each network request as they are
received from the server.
// To recieve data as a string we use TextDecoderStream class in pipethrough
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()
// To receive data as byte array we call getReader() directrly
const reader = response.body.getReader();
3. Create a loop to continue receiving messages until the done signal has been triggered. Inside this loop is also where you will update your frontend app with the SSE messages.
while (true) {
const {value, done} = await reader.read();
if (done) break;
console.log('Received', value);
}
Full Code Examples
Full example of receiving SSE messages from a
POST
request.
const response = await fetch('/sse', {
method: 'POST',
headers: {
'Content-Type': 'text/event-stream'
},
body: {
"user_id": 123
}
})
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()
while (true) {
const {value, done} = await reader.read();
if (done) break;
console.log('Received', value);
}
Sample
Express
server example to complete the loop for setup.
app.post('/completion', (req, res) => {
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Connection', 'keep-alive')
res.flushHeaders() // flush the headers to establish SSE with client
const response = openai.createCompletion({
model: 'text-davinci-003',
prompt: 'hello world',
max_tokens: 100,
temperature: 0,
stream: true
}, { responseType: 'stream' })
response.then(resp => {
resp.data.on('data', data => {
const lines = data.toString().split('\n').filter(line => line.trim() !== '')
for (const line of lines) {
const message = line.replace(/^data: /, '')
if (message === '[DONE]') {
res.write('data: DONE\n\n')
res.end()
return
}
const parsed = JSON.parse(message)
res.write(`data: ${parsed.choices[0].text}\n\n`)
}
})
}).catch(err => {
console.log(err)
})
})