Reworked error handling and pipeline functionality

This commit is contained in:
Yessiest 2024-05-11 22:55:12 +04:00
parent dd745ca123
commit 7b7d3c928a
7 changed files with 98 additions and 28 deletions

View File

@ -8,19 +8,58 @@ app = Landline::Server.new do
header "content-type", "text/plain" header "content-type", "text/plain"
"Hello World!" "Hello World!"
end end
get "/error" do
get "/error" do # This error will be caught by the default server handler
raise StandardError, "I raised an error! (and that's very sad)" raise StandardError, "I raised an error! (and that's very sad)"
end end
handle do |status, backtrace: nil|
page = ([Landline::Util::HTTP_STATUS[status]] + path "/handled" do
(backtrace || [""])).join("\n") get "/error" do # This error will be caught by the handler defined for this path
[ raise StandardError, "I raised an error! (and that's very sad)"
{ end
"content-length": page.bytesize,
"content-type": "text/plain" handle do |status, backtrace: nil, error: nil|
}, ([Landline::Util::HTTP_STATUS[status]] +
page (backtrace || [""])).join("\n")
] end
end
path "/pipelined" do
# The pipeline executes inside the function which catches errors,
# so it can catch errors before the handler for the current path.
# This may be useful in order to override the way things are output.
pipeline do |request, &output|
output.call(request)
rescue StandardError
throw :finish, "Internal error caught in pipeline!"
end
path "/inner" do
get "/error" do # This error will be caught by the pipeline
raise StandardError, "I raised an error! (and that's very sad)"
end
end
path "/inner_override" do
get "/error" do # This error will be caught by the innermost handler
raise StandardError, "I raised an error! (and that's very sad)"
end
handle do |status, backtrace: nil, error: nil|
([Landline::Util::HTTP_STATUS[status]] +
(backtrace || [""])).join("\n")
end
end
get "/error" do # This error will be caught by the pipeline
raise StandardError, "I raised an error! (and that's very sad)"
end
# This handler is preceded by the pipeline, but it may handle 404 codes.
handle do |status, backtrace: nil, error: nil|
([Landline::Util::HTTP_STATUS[status]] +
(backtrace || [""])).join("\n")
end
end end
end end

View File

@ -27,6 +27,7 @@ class HelloServer < Landline::App
page = ([Landline::Util::HTTP_STATUS[status]] + page = ([Landline::Util::HTTP_STATUS[status]] +
(backtrace || [""])).join("\n") (backtrace || [""])).join("\n")
[ [
status,
{ {
"content-length": page.bytesize, "content-length": page.bytesize,
"content-type": "text/plain", "content-type": "text/plain",

View File

@ -9,14 +9,17 @@ module Landline
# @param errorcode [Integer] # @param errorcode [Integer]
# @param backtrace [Array(String), nil] # @param backtrace [Array(String), nil]
# @raise [UncaughtThrowError] throws :finish to return back to Server # @raise [UncaughtThrowError] throws :finish to return back to Server
def die(errorcode, backtrace: nil) def die(errorcode, backtrace: nil, error: nil)
throw :finish, [errorcode].append( response = Landline::Response.convert(
*(@origin.properties["handle.#{errorcode}"] or (@origin.properties["handle.#{errorcode}"] or
@origin.properties["handle.default"]).call( @origin.properties["handle.default"]).call(
errorcode, errorcode,
backtrace: backtrace backtrace: backtrace,
) error: error
)
) )
response.status = errorcode if response.status == 200
throw :finish, response
end end
# (in Landline::Probe context) # (in Landline::Probe context)

View File

@ -37,6 +37,24 @@ module Landline
context.instance_exec(&setup) context.instance_exec(&setup)
end end
# (see ::Landline::Node#go)
def go(request)
# This is done to allow pipeline to interject handlers
# I'm more than willing to admit that this is stupid,
# but it is well worth the logical flexibility.
if ['handle.default', 'handle.505'].any? do |x|
@properties.storage.include? x
end
begin
super(request)
rescue StandardError => e
_die(request, 500, backtrace: [e.to_s] + e.backtrace, error: e)
end
else
super(request)
end
end
# Method callback on successful request navigation. # Method callback on successful request navigation.
# Finds the next appropriate path to go to. # Finds the next appropriate path to go to.
# @return [Boolean] true if further navigation will be done # @return [Boolean] true if further navigation will be done
@ -138,8 +156,6 @@ module Landline
return exit_stack(request, value) if value return exit_stack(request, value) if value
notfound(request) notfound(request)
rescue StandardError => e
_die(request, 500, backtrace: [e.to_s] + e.backtrace)
end end
# Run enqueued postprocessors on navigation failure # Run enqueued postprocessors on navigation failure
@ -175,16 +191,19 @@ module Landline
# @param errorcode [Integer] # @param errorcode [Integer]
# @param backtrace [Array(String), nil] # @param backtrace [Array(String), nil]
# @raise [UncaughtThrowError] throws :finish to stop processing # @raise [UncaughtThrowError] throws :finish to stop processing
def _die(request, errorcode, backtrace: nil) def _die(request, errorcode, backtrace: nil, error: nil)
proccontext = get_context(request) proccontext = get_context(request)
throw :finish, [errorcode].append( response = Landline::Response.convert(
*proccontext.instance_exec( proccontext.instance_exec(
errorcode, errorcode,
backtrace: backtrace, backtrace: backtrace,
error: error,
&(@properties["handle.#{errorcode}"] or &(@properties["handle.#{errorcode}"] or
@properties["handle.default"]) @properties["handle.default"])
) )
) )
response.status = errorcode if response.status == 200
throw :finish, response
end end
end end
end end

View File

@ -35,7 +35,7 @@ module Landline
def match(input) def match(input)
if @pattern.is_a? String if @pattern.is_a? String
input = Landline::PatternMatching.canonicalize(input) input = Landline::PatternMatching.canonicalize(input)
if input.start_with?(@pattern) if _match?(input)
[input.delete_prefix(@pattern), [], {}] [input.delete_prefix(@pattern), [], {}]
else else
false false
@ -51,7 +51,8 @@ module Landline
# @return [Boolean] # @return [Boolean]
def match?(input) def match?(input)
if @pattern.is_a? String if @pattern.is_a? String
Landline::PatternMatching.canonicalize(input).start_with? @pattern input = Landline::PatternMatching.canonicalize(input)
_match?(input)
else else
@pattern.match?(input) @pattern.match?(input)
end end
@ -59,6 +60,13 @@ module Landline
private private
def _match?(input)
parts = input.split("/")
@pattern.split("/").map.with_index do |part, index|
parts[index] == part
end.all?(true)
end
def patternify(pattern) def patternify(pattern)
classdomain = Landline::PatternMatching classdomain = Landline::PatternMatching
classdomain.constants classdomain.constants

View File

@ -58,14 +58,14 @@ module Landline
def setup_properties(*_args, **_opts) def setup_properties(*_args, **_opts)
{ {
"index" => [], "index" => [],
"handle.default" => proc do |code, backtrace: nil| "handle.default" => proc do |code, backtrace: nil, **_extra|
page = Landline::Util.default_error_page(code, backtrace) page = Landline::Util.default_error_page(code, backtrace)
headers = { headers = {
"content-length": page.bytesize, "content-length": page.bytesize,
"content-type": "text/html", "content-type": "text/html",
"x-cascade": true "x-cascade": true
} }
[headers, page] [code, headers, page]
end, end,
"path" => "/" "path" => "/"
}.each { |k, v| @properties[k] = v unless @properties[k] } }.each { |k, v| @properties[k] = v unless @properties[k] }

View File

@ -31,7 +31,7 @@ module Landline
@storage[key] = value @storage[key] = value
end end
attr_accessor :parent attr_accessor :parent, :storage
end end
# Read-only lookup proxy # Read-only lookup proxy