Browse Source

fix(sse): preserve empty data lines in SSE formatting

pull/15618/head
Matias Formoso 2 weeks ago
parent
commit
4a9561aff7
  1. 9
      fastapi/sse.py
  2. 14
      tests/test_sse.py

9
fastapi/sse.py

@ -88,7 +88,8 @@ class ServerSentEvent(BaseModel):
Use this when you need to send pre-formatted text, HTML fragments, Use this when you need to send pre-formatted text, HTML fragments,
CSV lines, or any non-JSON payload. The string is placed directly CSV lines, or any non-JSON payload. The string is placed directly
into the `data:` field as-is. into the `data:` field as-is. An empty string still emits a single
empty `data:` line.
Mutually exclusive with `data`. Mutually exclusive with `data`.
""" """
@ -213,7 +214,11 @@ def format_sse_event(
lines.append(f"event: {event}") lines.append(f"event: {event}")
if data_str is not None: if data_str is not None:
for line in data_str.splitlines(): # Normalize line endings and preserve empty data lines.
# This keeps explicit empty payloads (`data_str=""`) and trailing
# newlines represented as `data:` lines in SSE wire format.
normalized_data = data_str.replace("\r\n", "\n").replace("\r", "\n")
for line in normalized_data.split("\n"):
lines.append(f"data: {line}") lines.append(f"data: {line}")
if id is not None: if id is not None:

14
tests/test_sse.py

@ -6,7 +6,7 @@ import fastapi.routing
import pytest import pytest
from fastapi import APIRouter, FastAPI from fastapi import APIRouter, FastAPI
from fastapi.responses import EventSourceResponse from fastapi.responses import EventSourceResponse
from fastapi.sse import ServerSentEvent from fastapi.sse import ServerSentEvent, format_sse_event
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from pydantic import BaseModel from pydantic import BaseModel
@ -264,6 +264,18 @@ def test_data_and_raw_data_mutually_exclusive():
ServerSentEvent(data="json", raw_data="raw") ServerSentEvent(data="json", raw_data="raw")
def test_format_sse_event_keeps_empty_data_line():
"""An explicit empty payload should emit one `data:` line."""
payload = format_sse_event(data_str="")
assert payload == b"data: \n\n"
def test_format_sse_event_normalizes_crlf_and_keeps_trailing_empty_line():
"""CRLF and trailing newline should produce valid SSE data lines."""
payload = format_sse_event(data_str="first\r\nsecond\r\n")
assert payload == b"data: first\ndata: second\ndata: \n\n"
def test_sse_on_router_included_in_app(client: TestClient): def test_sse_on_router_included_in_app(client: TestClient):
response = client.get("/api/events") response = client.get("/api/events")
assert response.status_code == 200 assert response.status_code == 200

Loading…
Cancel
Save