在 Ruby/Sinatra 中,如何使用 ERB 模板和错误消息停止
In Ruby/Sinatra, how to halt with an ERB template and error message
在我的 Sinatra 项目中,我希望能够同时显示错误代码和错误消息来停止:
halt 403, "Message!"
我希望将其依次呈现在错误页面模板中(使用 ERB)。例如:
error 403 do
erb :"errors/error", :locals => {:message => env['sinatra.error'].message}
end
但是,显然 env['sinatra.error'].message
(又名自述文件和每个网站都说我应该这样做)没有公开我提供的消息。 (这段代码,当运行、returns时出现undefined method `message' for nil:NilClass
错误。)
我已经搜索了 4-5 个小时并试验了所有内容,但我无法弄清楚要通过 ERB 呈现消息的位置!有人知道在哪里吗?
(似乎我能想到的唯一选择就是写这个而不是上面的 halt
代码,每次我想停止:
halt 403, erb(:"errors/error", :locals => {m: "Message!"})
此代码有效。但这是一个混乱的解决方案,因为它涉及对错误 ERB 文件的位置进行硬编码。)
(如果您想知道,这个问题与 show_exceptions
配置标志无关,因为 set :show_exceptions, false
和 set :show_exceptions, :after_handler
没有区别。)
为什么它不起作用 − 使用源代码!
让我们看一下 Sinatra 源代码,看看为什么这个问题不起作用。 Sinatra 主文件 (lib/sinatra/base.rb
) 只有 2043 行,而且代码非常易读!
halt
所做的是:
def halt(*response)
response = response.first if response.length == 1
throw :halt, response
end
异常被捕获:
# Dispatch a request with error handling.
def dispatch!
invoke do
static! if settings.static? && (request.get? || request.head?)
filter! :before
route!
end
rescue ::Exception => boom
invoke { handle_exception!(boom) }
[..]
end
def handle_exception!(boom)
@env['sinatra.error'] = boom
[..]
end
但出于某种原因,此代码永远不会 运行(使用基本 "printf-debugging" 进行测试)。这是因为在 invoke
中块是 运行 像:
# Run the block with 'throw :halt' support and apply result to the response.
def invoke
res = catch(:halt) { yield }
res = [res] if Fixnum === res or String === res
if Array === res and Fixnum === res.first
res = res.dup
status(res.shift)
body(res.pop)
headers(*res)
elsif res.respond_to? :each
body res
end
nil # avoid double setting the same response tuple twice
end
注意这里的catch(:halt)
。 if Array === res and Fixnum === res.first
部分是 halt
设置的内容以及响应正文和状态代码的设置方式。
error 403 { .. }
块是 运行 在 call!
:
invoke { error_block!(response.status) } unless @env['sinatra.error']
所以现在我们明白了为什么这不起作用,我们可以寻找解决方案;-)
那么我可以用某种方式停止吗?
据我所知还没有。如果您查看 invoke
方法的主体,您会发现在使用 halt
时主体总是被设置。你不想要这个,因为你想覆盖响应主体。
解决方案
使用 "real" 异常而不是 halt
"pseudo-exception"。 Sinatra 似乎没有预定义的异常,但 handle_exception!
确实会查看 http_status
以设置正确的 HTTP 状态:
if boom.respond_to? :http_status
status(boom.http_status)
elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
status(boom.code)
else
status(500)
end
所以你可以使用这样的东西:
require 'sinatra'
class PermissionDenied < StandardError
def http_status; 403 end
end
get '/error' do
#halt 403, 'My special message to you!'
raise PermissionDenied, 'My special message to you!'
end
error 403 do
'Error message -> ' + @env['sinatra.error'].message
end
按预期工作(输出为 Error message -> My special message to you!
)。您可以在此处 return ERB 模板。
在 Sinatra v2.0.7+ 中,传递给 halt
的消息存储在响应正文中。因此,可以捕获带有错误代码和错误消息(例如:halt 403, "Message!"
)的 halt
并在错误页面模板中呈现:
error 403 do
erb :"errors/error", locals: { message: body[0] }
end
在我的 Sinatra 项目中,我希望能够同时显示错误代码和错误消息来停止:
halt 403, "Message!"
我希望将其依次呈现在错误页面模板中(使用 ERB)。例如:
error 403 do
erb :"errors/error", :locals => {:message => env['sinatra.error'].message}
end
但是,显然 env['sinatra.error'].message
(又名自述文件和每个网站都说我应该这样做)没有公开我提供的消息。 (这段代码,当运行、returns时出现undefined method `message' for nil:NilClass
错误。)
我已经搜索了 4-5 个小时并试验了所有内容,但我无法弄清楚要通过 ERB 呈现消息的位置!有人知道在哪里吗?
(似乎我能想到的唯一选择就是写这个而不是上面的 halt
代码,每次我想停止:
halt 403, erb(:"errors/error", :locals => {m: "Message!"})
此代码有效。但这是一个混乱的解决方案,因为它涉及对错误 ERB 文件的位置进行硬编码。)
(如果您想知道,这个问题与 show_exceptions
配置标志无关,因为 set :show_exceptions, false
和 set :show_exceptions, :after_handler
没有区别。)
为什么它不起作用 − 使用源代码!
让我们看一下 Sinatra 源代码,看看为什么这个问题不起作用。 Sinatra 主文件 (lib/sinatra/base.rb
) 只有 2043 行,而且代码非常易读!
halt
所做的是:
def halt(*response)
response = response.first if response.length == 1
throw :halt, response
end
异常被捕获:
# Dispatch a request with error handling.
def dispatch!
invoke do
static! if settings.static? && (request.get? || request.head?)
filter! :before
route!
end
rescue ::Exception => boom
invoke { handle_exception!(boom) }
[..]
end
def handle_exception!(boom)
@env['sinatra.error'] = boom
[..]
end
但出于某种原因,此代码永远不会 运行(使用基本 "printf-debugging" 进行测试)。这是因为在 invoke
中块是 运行 像:
# Run the block with 'throw :halt' support and apply result to the response.
def invoke
res = catch(:halt) { yield }
res = [res] if Fixnum === res or String === res
if Array === res and Fixnum === res.first
res = res.dup
status(res.shift)
body(res.pop)
headers(*res)
elsif res.respond_to? :each
body res
end
nil # avoid double setting the same response tuple twice
end
注意这里的catch(:halt)
。 if Array === res and Fixnum === res.first
部分是 halt
设置的内容以及响应正文和状态代码的设置方式。
error 403 { .. }
块是 运行 在 call!
:
invoke { error_block!(response.status) } unless @env['sinatra.error']
所以现在我们明白了为什么这不起作用,我们可以寻找解决方案;-)
那么我可以用某种方式停止吗?
据我所知还没有。如果您查看 invoke
方法的主体,您会发现在使用 halt
时主体总是被设置。你不想要这个,因为你想覆盖响应主体。
解决方案
使用 "real" 异常而不是 halt
"pseudo-exception"。 Sinatra 似乎没有预定义的异常,但 handle_exception!
确实会查看 http_status
以设置正确的 HTTP 状态:
if boom.respond_to? :http_status
status(boom.http_status)
elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
status(boom.code)
else
status(500)
end
所以你可以使用这样的东西:
require 'sinatra'
class PermissionDenied < StandardError
def http_status; 403 end
end
get '/error' do
#halt 403, 'My special message to you!'
raise PermissionDenied, 'My special message to you!'
end
error 403 do
'Error message -> ' + @env['sinatra.error'].message
end
按预期工作(输出为 Error message -> My special message to you!
)。您可以在此处 return ERB 模板。
在 Sinatra v2.0.7+ 中,传递给 halt
的消息存储在响应正文中。因此,可以捕获带有错误代码和错误消息(例如:halt 403, "Message!"
)的 halt
并在错误页面模板中呈现:
error 403 do
erb :"errors/error", locals: { message: body[0] }
end