-
This took me longer than it should have to figure out, so leaving a note here for future reference.
-
Context
-
Sometimes we may want to stream content into a web page as it arrives. The best example of this is generations from AI APIs, where you want the text to appear as it is received from the API. Web browsers have a feature called Server Sent Events that allow us to do this. Rails lets us send these responses from our controller, and htmx lets us stream the responses into our page.
-
Steps
-
Add the htmx SSE plugin. Make sure that it's loading after the main htmx file, and make sure the version reflects your version of htmx - they changed the syntax after 1.9.9
-
<script src="/vendor/htmx/2.0.4.js" defer ></script> <script src="https://unpkg.com/htmx-ext-sse@2.2.2/sse.js" defer></script>
-
In your view, create a div that fetches a streaming response from the server.
-
<div hx-disinherit="*"> <div hx-ext="sse" sse-connect="<%= demo_stream_chat_path %>" sse-swap="message" sse-swap-style="append" hx-swap="beforeend" sse-close="complete"> The response is: </div> </div>
-
What's happening here: We're saying "open a connection to
/chat_demo
, then for every message you receive, put it into this div, and once you receive a message calledcomplete
, close the connection. -
Now that our view is set up, we'll create a controller action to send content back.
-
Syntax A
-
This is more close to the bare metal, but it can be brittle. It will break if you either leave out the
Last-Modified
header, misformat the string, or leave out the trailing\n\n
s in the string -
class StreamingController < ApplicationController include ActionController::Live def stream_chat response.headers['Content-Type'] = 'text/event-stream' response.headers['Last-Modified'] = Time.now.httpdate response.stream.write("data: Message 1\n\n") sleep 2 response.stream.write("data: Message 2\n\n") sleep 2 response.stream.write("data: Message 3\n\n") response.stream.write("event: complete\ndata: close\n\n") ensure response.stream.close end end
-
Syntax B
-
class StreamingController < ApplicationController include ActionController::Live def stream_chat response.headers['Content-Type'] = 'text/event-stream' response.headers['Last-Modified'] = Time.now.httpdate sse = SSE.new(response.stream) sse.write('Message 1') sleep 2 sse.write('Message 2') sleep 2 sse.write('Message 3') sse.write('done', event: 'complete') ensure sse.close end end
-