Skip to content

Commit 921dc3e

Browse files
authored
Log route handler errors and answer them with a 500 page (#3621)
`run!` hands each request to `HTTP.streamhandler`, so the `try` around it also covers Pluto's route handlers, and `is_disconnection_exception` treats any `IOError` or `SystemError` as the client hanging up. An exception from a handler, such as `POST /notebookupload` failing to write the uploaded file, was dropped without being logged: HTTP.jl 2 answered `200` with an empty body, HTTP.jl 1 a bare `500`. `internal_error_middleware` catches what the handlers throw, logs it, and answers with Pluto's `500` error page, which the frontend shows when an upload fails. It sits inside `default_headers_middleware`, so the error page gets the same headers as any other response, and the outer `try` only sees failures to read the request or write the response.
1 parent 38496f0 commit 921dc3e

2 files changed

Lines changed: 25 additions & 1 deletion

File tree

‎src/webserver/WebServer.jl‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,21 @@ function create_binder_token_middleware(session::ServerSession)
146146
end
147147
end
148148

149+
"""
150+
Middleware that turns an exception thrown while handling a request into a `500` error page,
151+
and logs it. The details only go to the log, since the request might not be authenticated.
152+
"""
153+
function internal_error_middleware(handler)
154+
function (request::HTTP.Request)
155+
try
156+
handler(request)
157+
catch e
158+
@error "Failed to handle request" request.method path = HTTP.URI(request.target).path exception = (e, catch_backtrace())
159+
error_response(500, "Internal server error", "Pluto ran into an unexpected error while handling this request. The details are in the terminal where Pluto is running. Please <a href='https://github.com/JuliaPluto/Pluto.jl/issues'>report this error</a>!")
160+
end
161+
end
162+
end
163+
149164
"Middleware that adds the headers that Pluto sets on every HTTP response."
150165
function default_headers_middleware(handler)
151166
function (request::HTTP.Request)
@@ -173,7 +188,7 @@ function run!(session::ServerSession)
173188
pluto_router = http_router_for(session)
174189
store_session_middleware = create_session_context_middleware(session)
175190
store_binder_token_middleware = create_binder_token_middleware(session)
176-
app = pluto_router |> auth_middleware |> store_session_middleware |> store_binder_token_middleware |> default_headers_middleware
191+
app = pluto_router |> auth_middleware |> store_session_middleware |> store_binder_token_middleware |> internal_error_middleware |> default_headers_middleware
177192
# `HTTP.streamhandler` reads the request body, calls `app`, and writes the response.
178193
streamed_app = HTTP.streamhandler(app)
179194

‎test/webserver.jl‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,15 @@ end
108108
@test [c.code for c in notebook.cells] == codes
109109
end
110110

111+
@testset "handler error" begin
112+
# The upload cannot be saved in a directory that does not exist, so the route handler throws.
113+
r = HTTP.post(local_url("notebookupload?name=directory-that-does-not-exist/uploaded"); body="x = 1", status_exception=false)
114+
@test r.status == 500
115+
@test startswith(HTTP.header(r, "Content-Type"), "text/html")
116+
@test HTTP.header(r, "Content-Length") == string(length(r.body))
117+
@test length(🍭.notebooks) == 1
118+
end
119+
111120
@testset "websocket" begin
112121
notebook = only(values(🍭.notebooks))
113122
reply = Ref{Any}(nothing)

0 commit comments

Comments
 (0)