Skip to content

Commit

Permalink
throw better errors when app errors and fix hang (#294)
Browse files Browse the repository at this point in the history
* throw better errors when app errors

* small improvements

* show nice errors in more places

* add back mark_displayed!
  • Loading branch information
SimonDanisch authored Feb 18, 2025
1 parent eb02ecb commit 26b8a1f
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 10 deletions.
7 changes: 4 additions & 3 deletions src/HTTPServer/helper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ function response_404(body="Not Found")
], body=body)
end

function response_500(exception)
body = sprint(io-> Base.showerror(io, exception))
function response_500(html)
return HTTP.Response(404, [
"Content-Type" => "text/html",
"charset" => "utf-8",
# Avoids throwing lots of errors in the devtools console when
# VSCode tries to load non-existent resources from the plots pane.
"Access-Control-Allow-Origin" => "*",
], body=body)
];
body=html,
)
end

# TODO, how to do this without pircay? THis happens inside HTTP, so we can't just use try & catch in our own code
Expand Down
61 changes: 59 additions & 2 deletions src/HTTPServer/implementation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,58 @@ function apply_handler(chain::Tuple, context, args...)
return apply_handler(Base.tail(chain), context, result...)
end

function linkify_stacktrace(bt::String)
lines = split(bt, '\n'; keepempty=false) # Split stack trace into lines
elements = []

for line in lines
# Match both Windows (C:\path\file.jl:123) and Unix (/path/file.jl:123) paths
m = match(r"^(.*?)([A-Za-z]:[\\/][^\s]+\.jl|\.[\\/][^\s]+\.jl):(\d+)(.*)", line)
if m !== nothing
prefix, file, line_num, suffix = m.captures
normalized_file = replace(file, "\\" => "/") # Convert Windows paths to `/`
vscode_url = "vscode://file/" * normalized_file * ":" * line_num # VS Code link
push!(
elements,
DOM.span(
String(prefix),
DOM.a(file * ":" * line_num; href=vscode_url),
String(suffix),
),
DOM.br(),
)
else
m2 = match(r"^(.*?)(\[\d+\])", line)
if !isnothing(m2)
prefix, suffix = m2.captures
push!(
elements,
DOM.span(String(line); style="color: darkred; font-weight: bold;"),
DOM.br(),
) # Normal line
else
push!(elements, DOM.span(String(line)), DOM.br()) # Normal line
end
end
end
return DOM.pre(
elements...; class="backtrace", style="white-space: nowrap; overflow-x: auto;"
)
end

function err_to_html(err, stacktrace)
error_msg = sprint() do io
Base.showerror(io, err)
end
stacktrace_msg = sprint() do io
iol = IOContext(io, :stacktrace_types_limited => Base.RefValue(true))
Base.show_backtrace(iol, stacktrace)
end
return DOM.div(
DOM.h1(error_msg; style="color: red;"), linkify_stacktrace(stacktrace_msg)
)
end

function delegate(routes::Routes, application, request::Request, args...)
try
for (pattern, f) in routes.table
Expand All @@ -107,9 +159,14 @@ function delegate(routes::Routes, application, request::Request, args...)
# What a classic this response!
return response_404("Didn't find route for $(request.target)")
catch e
err = CapturedException(e, Base.catch_backtrace())
stacktrace = Base.catch_backtrace()
err = CapturedException(e, stacktrace)
Base.showerror(stderr, err)
return response_500(err)
html = err_to_html(e, stacktrace)
html_str = sprint() do io
Bonito.print_as_page(io, html)
end
return response_500(html_str)
end
end

Expand Down
17 changes: 12 additions & 5 deletions src/app.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ function update_app!(parent::Session, new_app::App)
update_session_dom!(parent, "subsession-application-dom", new_app)
end


function rendered_dom(session::Session, app::App, target=HTTP.Request())
app.session[] = session
session.current_app[] = app
dom = Base.invokelatest(app.handler, session, target)
return jsrender(session, dom)
try
dom = Base.invokelatest(app.handler, session, target)
return jsrender(session, dom)
catch err
html = HTTPServer.err_to_html(err, Base.catch_backtrace())
return jsrender(session, html)
end
end

function bind_global(session::Session, var::AbstractObservable{T}) where T
Expand All @@ -57,9 +63,8 @@ function serve_app(app, context)
html_str = sprint() do io
page_html(io, session, html_dom)
end
response = html(html_str)
mark_displayed!(session)
return response
return html(html_str)
end

# Enable route!(server, "/" => app)
Expand Down Expand Up @@ -161,5 +166,7 @@ end
function wait_for_ready(app::App; timeout=100)
wait_for(()-> !isnothing(app.session[]); timeout=timeout)
isclosed(app.session[]) && return nothing
wait_for(()-> isready(app.session[]); timeout=timeout)
wait_for(timeout=timeout) do
isready(app.session[]) || isclosed(app.session[])
end
end

0 comments on commit 26b8a1f

Please sign in to comment.