Search code examples
fastapiserver-sent-eventshtmxstarlette

How to set the event for when sending SSE via FastAPI/Starlette to have different events on the same stream


I want to send SSE from FastAPI with different events on a single stream and receive them using HTMX-sse in the browser.

The recommended way is to use sse-starlette.

Full working example at https://devdojo.com/bobbyiliev/how-to-use-server-sent-events-sse-with-fastapi

Simplified here

import asyncio
import uvicorn
from fastapi import FastAPI, Request
    
app = FastAPI()
    
from sse_starlette.sse import EventSourceResponse
    
STREAM_DELAY = 1  # second
    
@app.get('/stream')
async def message_stream(request: Request):

    cnt = 0

    async def event_generator():
        while True:
            # If client closes connection, stop sending events
            if await request.is_disconnected():
                break

            cnt += 1
   
            # note: I would like to define the event name here, too
   
            yield f"I am the data part {cnt}\nand i am multiline"
    
            await asyncio.sleep(STREAM_DELAY)
    
    return EventSourceResponse(event_generator())

This code works fine with one problem.

The EventSourceResponse or event_generator function not allow to define the event needed by ServerSentEvent further down the code.

The SSE content messages pushed should look like this:

event: EventName
data: The data to push

The event name is optional.

Sending an event name allows the receiver to decide what to do with the message. (I'm using HTMX https://htmx.org/attributes/hx-sse/ to receive the messages.)

The content sent by this example is

data: I am the data part 1
data: and i am multiline
data: I am the data part 2
data: and i am multiline
data: I am the data part 3
data: and i am multiline
data: I am the data part 4
data: and i am multiline
...

And the data: prefix is added to each content message. I can't remove it. Also I can't add line breaks, each line break creates a new data: prefix.

I would like to send different events on the same stream. The data part need to allow line breaks.

My data should look like this:

event: main
data: I am the data part 1\nand I am multiline

event: subform
data: I am the data part 2\nand I am multiline

event: main
data: I am the data part 3\nand I am multiline

event: subform
data: I am the data part 4\nand I am multiline

How can I get the EventSourceResponse to stop adding a data: prefix, so that the generator can yield both prefix and context. And send multiline data in one event block?


Solution

  • I found the documentation regarding sse-starlette a bit lacking, but from the examples provided I figured out the following.

    To set the event type you have two options:

    async def gen():
        # Either a dict
        yield {'event': 'EventName', 'data': 'Event Payload'}
        # Or a ServerSentEvent
        yield ServerSentEvent('Event Payload', event='EventName')
    

    I'm not sure it's possible to directly send multiline data in a SSE, so I suggest encoding the data. Seems like htmx can accept plain HTML from a SSE, so you can encode your newlines as <br> or similar. Otherwise you could attempt to encode the payload in JSON, but you'd have to decode it on the client again.