You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+9Lines changed: 9 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -56,6 +56,15 @@ HTTP.open(:GET, "https://tinyurl.com/bach-cello-suite-1-ogg") do http
56
56
end
57
57
```
58
58
59
+
Handle [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) (SSE) streams by passing an `sse_callback` function to `HTTP.request`:
Copy file name to clipboardExpand all lines: docs/src/client.md
+28Lines changed: 28 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -233,6 +233,34 @@ end -> HTTP.Response
233
233
234
234
Where the `io` argument provided to the function body is an `HTTP.Stream` object, a custom `IO` that represents an open connection that is ready to be written to in order to send the request body, and/or read from to receive the response body. Note that `startread(io)` should be called before calling `readavailable` to ensure the response status line and headers are received and parsed appropriately. Calling `eof(io)` will return true until the response body has been completely received. Note that the returned `HTTP.Response` from `HTTP.open` will _not_ have a `.body` field since the body was read in the function body.
235
235
236
+
### Server-Sent Events
237
+
238
+
HTTP.jl can parse [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) streams directly via the `sse_callback` keyword on `HTTP.request`. When this keyword is supplied, HTTP.jl incrementally parses the incoming bytes as an event stream and invokes the callback with an `HTTP.SSEEvent` struct for every event:
The callback can be `f(event)` or `f(stream, event)`. The two-argument form allows cancelling the request by calling `close(stream)` (for example, in response to a specific event).
251
+
252
+
Each callback receives a `SSEEvent` with the following fields:
253
+
254
+
-`data::String`: newline-joined `data:` payload for the event (with the trailing newline removed).
255
+
-`event::Union{Nothing,String}`: the most recent `event:` field, or `nothing` when not provided (equivalent to the default `"message"` type).
256
+
-`id::Union{Nothing,String}`: the last `id:` value observed, automatically persisted between events per the SSE specification.
257
+
-`retry::Union{Nothing,Int}`: the last `retry:` directive in milliseconds, propagated to subsequent events until another `retry:` value is parsed.
258
+
-`fields::Dict{String,String}`: newline-joined string values for every field encountered since the previous event, including custom non-standard fields.
259
+
260
+
Because HTTP.jl streams the response directly to the callback, the returned `HTTP.Response` will always have `response.body === HTTP.nobody`. The `sse_callback` keyword cannot be combined with `response_stream` or a custom `iofunction`. The callback is only invoked for non-error responses; error responses are read like a normal request, and `status_exception` behavior applies. Parsing or callback errors surface as regular request errors (`HTTP.RequestError`) with the underlying exception in `err.error`. Compressed streams are supported automatically unless `decompress=false` is explicitly set.
261
+
262
+
For a full end-to-end example, see [`docs/examples/server_sent_events.jl`](https://github.com/JuliaWeb/HTTP.jl/blob/master/docs/examples/server_sent_events.jl).
263
+
236
264
### Download
237
265
238
266
A [`download`](@ref) function is provided for similar functionality to `Downloads.download`.
Copy file name to clipboardExpand all lines: docs/src/server.md
+72-1Lines changed: 72 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -108,8 +108,79 @@ Lower-level core server functionality that only operates on `HTTP.Stream`. Provi
108
108
109
109
Nginx-style log formatting is supported via the [`HTTP.@logfmt_str`](@ref) macro and can be passed via the `access_log` keyword argument for [`HTTP.listen`](@ref) or [`HTTP.serve`](@ref).
110
110
111
+
## Server-Sent Events (SSE)
112
+
113
+
HTTP.jl provides built-in support for [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events), a standard for pushing real-time updates from server to client over HTTP.
114
+
115
+
### Creating an SSE Response
116
+
117
+
Use [`HTTP.sse_stream`](@ref) to create an SSE stream from a response object:
118
+
119
+
```julia
120
+
using HTTP
121
+
122
+
HTTP.serve() do request
123
+
response = HTTP.Response(200)
124
+
HTTP.sse_stream(response) do stream
125
+
for i in1:5
126
+
write(stream, HTTP.SSEEvent("Event $i"))
127
+
sleep(1)
128
+
end
129
+
end
130
+
131
+
return response
132
+
end
133
+
```
134
+
135
+
The `sse_stream` function:
136
+
1. Creates an `SSEStream` for writing events
137
+
2. Sets the response body to the stream
138
+
3. Adds required headers: `Content-Type: text/event-stream` and `Cache-Control: no-cache`
139
+
4. Uses a bounded internal buffer (configurable via `max_len`, default 16 MiB) to provide backpressure if the client is slow to read
140
+
5. Spawns a task to run the body of the do-block asynchronously
141
+
6. Closes the stream when the do-block completes
142
+
143
+
### Writing Events
144
+
145
+
Write events using `write(stream, HTTP.SSEEvent(...))`:
146
+
147
+
```julia
148
+
# Simple data-only event
149
+
write(stream, HTTP.SSEEvent("Hello, world!"))
150
+
151
+
# Event with type (for client-side addEventListener)
-`event::Union{Nothing,String}`: Event type name (maps to `addEventListener` on client)
172
+
-`id::Union{Nothing,String}`: Event ID for reconnection tracking
173
+
-`retry::Union{Nothing,Int}`: Suggested reconnection delay in milliseconds
174
+
175
+
### Important Notes
176
+
177
+
- The do-block spawns a task where events will be written asynchronously
178
+
- The handler must return the response while events are written asynchronously
179
+
- Events will not actually be sent to the client until the handler has returned the response
180
+
- For client-side SSE consumption, see the [Client documentation](client.md#Server-Sent-Events)
181
+
111
182
## Serving on the interactive thead pool
112
183
113
184
Beginning in Julia 1.9, the main server loop is spawned on the [interactive threadpool](https://docs.julialang.org/en/v1.9/manual/multi-threading/#man-threadpools) by default. If users do a Threads.@spawn from a handler, those threaded tasks should run elsewhere and not in the interactive threadpool, keeping the web server responsive.
114
185
115
-
Note that just having a reserved interactive thread doesn’t guarantee CPU cycles, so users need to properly configure their running Julia session appropriately (i.e. ensuring non-interactive threads available to run tasks, etc).
186
+
Note that just having a reserved interactive thread doesn’t guarantee CPU cycles, so users need to properly configure their running Julia session appropriately (i.e. ensuring non-interactive threads available to run tasks, etc).
0 commit comments