SSE Implementation in Fullmoon
I was testing Fullmoon and when I focused on SSE I had some issues.
Issue 1: fullmoon.streamContent("sse", {...}) Causes Type Error
When attempting to use the fullmoon.streamContent("sse", {...}) pattern for Server-Sent Events, an error occurs in fullmoon.lua:1463:
./.lua/fullmoon.lua:1463: bad argument #1 to ? (string expected)
The problematic code in fullmoon.lua around line 1463 appears to be:
local function streamWrap(func) return function(...) return coroutine.yield(func(...)()) or true end
This fails when passing "sse" as the first argument to streamContent().
Issue 2: Returning Empty String from Route Handler Closes SSE Connection
When implementing a manual workaround using coroutines, returning an empty string from the route handler causes Redbean to add a Content-Length: 0 header, which makes browsers immediately close the SSE connection.
Reproduction Steps
- Set up a Fullmoon route that attempts to use SSE:
fullmoon.setRoute("/api/sse", function(r) -- Set appropriate headers SetHeader("Content-Type", "text/event-stream") SetHeader("Cache-Control", "no-cache")
-- Method 1: Using streamContent - causes error in fullmoon.lua:1463
return fullmoon.streamContent("sse", {
event = "ping",
data = "test"
})
-- Method 2: Manual coroutine - adds Content-Length and closes connection
local co = coroutine.create(function()
Write("data: test\n\n")
coroutine.yield()
end)
coroutine.resume(co)
return ""
Current Workaround
I implemented a working solution, but it requires several non-obvious steps:
fullmoon.setRoute("/api/sse", function(r) -- Set headers SetHeader("Content-Type", "text/event-stream") SetHeader("Cache-Control", "no-cache")
-- Create coroutine
local co = coroutine.create(function()
Write("data: test\n\n")
coroutine.yield()
-- More SSE code...
-- CRITICAL: These two lines prevent Content-Length from being set
SetStatus(200)
SetHeader("Transfer-Encoding", "chunked")
-- Start coroutine
coroutine.resume(co)
-- CRITICAL: Must return true, not a string
return true
Expected Behavior
- fullmoon.streamContent("sse", {...}) should work correctly for SSE
- There should be a simpler way to maintain an SSE connection in Fullmoon
Suggested Solution
Add a dedicated SSE helper to Fullmoon that handles these details, something like:
fullmoon.serveSSE(function() -- Simple SSE implementation that handles coroutine, chunked encoding -- and proper response handling automatically Write("data: test\n\n") coroutine.yield()
Environment
- Redbean version: 3.0.0
- Fullmoon version: 0.384
- Test browsers: Safari, Chrome
@stet, I may be missing something in what you're trying to do, but you can't really return streamContent(), as it's most likely doesn't do what you need (not your issue for sure, as these aspects are not well documented). You can only return from the sse route when all the SSE-related work is done and you return in the same way as you return from all other routes (by executing a serve* request or returning true or an empty string). At that point all the work is already done and it's not possible in redbean to "re-enter" the handler again. That's why the streamContent method does this implicitly for you, allowing to send some content back, but still retaining control in the Lua handler until all the processing is done. See the example in examples/htmxsse.lua.
I will look into providing better error reporting for this case, although it doesn't seem to be immediately available.