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"
"Hello World!"
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)"
end
handle do |status, backtrace: nil|
page = ([Landline::Util::HTTP_STATUS[status]] +
path "/handled" do
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
handle do |status, backtrace: nil, error: nil|
([Landline::Util::HTTP_STATUS[status]] +
(backtrace || [""])).join("\n")
[
{
"content-length": page.bytesize,
"content-type": "text/plain"
},
page
]
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

View File

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

View File

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

View File

@ -37,6 +37,24 @@ module Landline
context.instance_exec(&setup)
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.
# Finds the next appropriate path to go to.
# @return [Boolean] true if further navigation will be done
@ -138,8 +156,6 @@ module Landline
return exit_stack(request, value) if value
notfound(request)
rescue StandardError => e
_die(request, 500, backtrace: [e.to_s] + e.backtrace)
end
# Run enqueued postprocessors on navigation failure
@ -175,16 +191,19 @@ module Landline
# @param errorcode [Integer]
# @param backtrace [Array(String), nil]
# @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)
throw :finish, [errorcode].append(
*proccontext.instance_exec(
response = Landline::Response.convert(
proccontext.instance_exec(
errorcode,
backtrace: backtrace,
error: error,
&(@properties["handle.#{errorcode}"] or
@properties["handle.default"])
)
)
response.status = errorcode if response.status == 200
throw :finish, response
end
end
end

View File

@ -35,7 +35,7 @@ module Landline
def match(input)
if @pattern.is_a? String
input = Landline::PatternMatching.canonicalize(input)
if input.start_with?(@pattern)
if _match?(input)
[input.delete_prefix(@pattern), [], {}]
else
false
@ -51,7 +51,8 @@ module Landline
# @return [Boolean]
def match?(input)
if @pattern.is_a? String
Landline::PatternMatching.canonicalize(input).start_with? @pattern
input = Landline::PatternMatching.canonicalize(input)
_match?(input)
else
@pattern.match?(input)
end
@ -59,6 +60,13 @@ module Landline
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)
classdomain = Landline::PatternMatching
classdomain.constants

View File

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

View File

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