在Ruby/Sinatra中,如何使用ERB模板和错误消息暂停

Bra*_*ang 5 ruby erb sinatra

在我的Sinatra项目中,我希望能够同时停止错误代码和错误消息:

halt 403, "Message!"
Run Code Online (Sandbox Code Playgroud)

反过来,我想要在错误页面模板中呈现(使用ERB).例如:

error 403 do
    erb :"errors/error", :locals => {:message => env['sinatra.error'].message}
end
Run Code Online (Sandbox Code Playgroud)

然而,显然env['sinatra.error'].message(也就是自述文件和每个网站都说我应该这样做)并没有暴露我提供的信息.(此代码在运行时返回undefined method `message' for nil:NilClass错误.)

我已经搜索了4-5个小时并尝试了所有内容,但我无法弄清楚消息通过ERB呈现给我的位置!有谁知道它在哪里?


(似乎我能想到的唯一选择是写这个而不是halt上面的代码,每次我想停止:

halt 403, erb(:"errors/error", :locals => {m: "Message!"})
Run Code Online (Sandbox Code Playgroud)

这段代码有效.但这是一个混乱的解决方案,因为它涉及硬编码错误ERB文件的位置.)

(如果你想知道,这个问题是不相关的show_exceptions,因为这两个配置标记set :show_exceptions, false,并set :show_exceptions, :after_handler没有什么区别.)

Mar*_*oij 6

为什么不起作用 - 使用源代码!

让我们看看Sinatra源代码,看看为什么这个问题不起作用.主要的Sinatra文件(lib/sinatra/base.rb)只有2043行,并且代码相当可读!

一切halt都是:

def halt(*response)
  response = response.first if response.length == 1
  throw :halt, response
end
Run Code Online (Sandbox Code Playgroud)

例外情况包括:

# 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
Run Code Online (Sandbox Code Playgroud)

但由于某种原因,这段代码永远不会运行(通过基本的"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
Run Code Online (Sandbox Code Playgroud)

请注意catch(:halt)这里.该if Array === res and Fixnum === res.first部分是halt设置以及如何设置响应正文和状态代码的部分.

error 403 { .. }块运行于call!:

invoke { error_block!(response.status) } unless @env['sinatra.error']
Run Code Online (Sandbox Code Playgroud)

所以现在我们明白为什么这不起作用,我们可以寻找解决方案;-)

那么我可以用某种方式停止吗?

不是我能看到的.如果你看一下invoke方法的主体,你会发现在使用时总是设置了主体halt.您不希望这样,因为您想要覆盖响应正文.

使用"真实"例外而不是halt"伪例外".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
Run Code Online (Sandbox Code Playgroud)

所以你可以使用这样的东西:

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
Run Code Online (Sandbox Code Playgroud)

哪个按预期工作(输出是Error message -> My special message to you!).您可以在此处返回ERB模板.