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
HTTP.jl 2.0 is a breaking rewrite on the Reseau transport layer. This adapts
Pluto's web server to the new API and drops pre-2.0 compatibility.
- Require `HTTP = "2.1"`. The mixed HTTP+WebSocket server helper
`HTTP.WebSockets.upgrade(f, stream)` is not in 2.0.0 (added in 2.1.0), and
Pluto serves HTTP and WebSockets on one port, so 2.0.0 cannot be supported.
- WebServer.jl: `listen!` no longer takes `server=`/`on_shutdown`/`stream`/
`verbose`, so bind the `TCP.Listener` ourselves (keeping the port-hint
search) and pass it to `listen!`. The graceful `close(::HTTP.Server)` waits
for active WebSocket connections, so client shutdown now runs from
`RunningPlutoServer` before the server is closed.
- The server `Stream` exposes request metadata via `http.message`; rebuild the
request with its body for the handlers, then assign `http.response` and write
the body bytes (mirrors HTTP's own stream handler). A client disconnect now
surfaces as `SystemError` (Reseau), so it is swallowed alongside `IOError`.
- `HTTP.WebSocket`/`HTTP.send` move under `HTTP.WebSockets`. Keep 1.x behavior
of not checking the WebSocket `Origin` (it breaks proxied setups; the secret
is the real auth).
- `auth_middleware`: `Headers` is no longer a plain vector, so replace the
`filter!` with `setheader`, which already de-duplicates.
- `readtimeout` -> `read_idle_timeout`.
- test/Configuration.jl: the cookie jar keyword is `cookiejar`, not `jar`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
error("Cannot listen on port $port. It may already be in use, or you may not have sufficient permissions. Use Pluto.run() to automatically select an available port.")
79
99
end
100
+
return port, serversocket
80
101
end
81
-
return port, serversocket
82
102
end
83
103
84
104
struct RunningPlutoServer
85
105
http_server
106
+
on_shutdown::Function
86
107
initial_registry_update_task::Task
87
108
end
88
109
89
110
function Base.close(ssc::RunningPlutoServer)
111
+
# Close client connections and shut down notebooks first: the graceful
112
+
# `close(::HTTP.Server)` waits for active (WebSocket) connections to finish.
113
+
ssc.on_shutdown()
90
114
close(ssc.http_server)
91
115
wait(ssc.http_server)
92
116
wait(ssc.initial_registry_update_task)
@@ -101,10 +125,11 @@ function Base.wait(ssc::RunningPlutoServer)
101
125
catch e
102
126
println()
103
127
println()
104
-
Base.close(ssc)
105
128
(e isa InterruptException) ||rethrow(e)
129
+
finally
130
+
Base.close(ssc)
106
131
end
107
-
132
+
108
133
nothing
109
134
end
110
135
@@ -149,20 +174,18 @@ function run!(session::ServerSession)
149
174
local port, serversocket =port_serversocket(hostIP, favourite_port, port_hint)
150
175
151
176
on_shutdown() =@syncbegin
152
-
# Triggered by HTTP.jl
153
177
@info("\nClosing Pluto... Restart Julia for a fresh session. \n\nHave a nice day! 🎈\n\n")
# "upgrade" means accept and start the websocket connection that the client requested
185
-
HTTP.WebSockets.upgrade(http) do clientstream
208
+
# Origin checking is disabled (like HTTP.jl 1.x) because it breaks proxied setups (Binder, JupyterHub); Pluto's own secret provides the authentication.
209
+
HTTP.WebSockets.upgrade(http; check_origin=(request, origin) ->true) do clientstream
186
210
if HTTP.WebSockets.isclosed(clientstream)
187
211
return
188
212
end
@@ -248,7 +272,7 @@ function run!(session::ServerSession)
248
272
end
249
273
finally
250
274
# if we never wrote a response, then do it now
251
-
ifisopen(http)&&!iswritable(http)
275
+
ifisopen(http)
252
276
finish()
253
277
end
254
278
end
@@ -258,9 +282,19 @@ function run!(session::ServerSession)
258
282
else
259
283
# then it's a regular HTTP request, not a WS upgrade
260
284
261
-
request::HTTP.Request= http.message
262
-
request.body =read(http)
263
-
# HTTP.closeread(http)
285
+
# `http.message` only carries the request metadata; rebuild a request that includes the body for the handlers.
286
+
request::HTTP.Request=let m = http.message
287
+
HTTP.Request(
288
+
m.method,
289
+
m.target;
290
+
headers=m.headers,
291
+
body=read(http),
292
+
host=m.host,
293
+
proto_major=Int(m.proto_major),
294
+
proto_minor=Int(m.proto_minor),
295
+
close=m.close,
296
+
)
297
+
end
264
298
265
299
# If a "token" url parameter is passed in from binder, then we store it to add to every URL (so that you can share the URL to collaborate).
266
300
let
@@ -271,23 +305,27 @@ function run!(session::ServerSession)
0 commit comments