【问题标题】:In Ruby/Sinatra, how to halt with an ERB template and error message在 Ruby/Sinatra 中,如何使用 ERB 模板和错误消息停止
【发布时间】:2016-07-07 17:25:17
【问题描述】:

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

halt 403, "Message!"

我希望这反过来在错误页面模板中呈现(使用 ERB)。例如:

error 403 do
    erb :"errors/error", :locals => {:message => env['sinatra.error'].message}
end

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

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


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

halt 403, erb(:"errors/error", :locals => {m: "Message!"})

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

(如果您想知道,这个问题与show_exceptions 配置标志无关,因为set :show_exceptions, falseset :show_exceptions, :after_handler 没有区别。)

【问题讨论】:

    标签: ruby sinatra erb


    【解决方案1】:

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

    让我们看看 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 时始终设置主体。你不想要这个,因为你想覆盖响应正文。

    解决方案

    使用“真正的”异常,而不是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
    

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

    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 -&gt; My special message to you!)。您可以在此处返回 ERB 模板。

    【讨论】:

      【解决方案2】:

      在 Sinatra v2.0.7+ 中,传递给 halt 的消息存储在响应正文中。因此,可以在错误页面模板中捕获并呈现带有错误代码和错误消息(例如:halt 403, "Message!")的halt

      error 403 do
        erb :"errors/error", locals: { message: body[0] }
      end
      

      【讨论】:

        猜你喜欢
        • 2018-01-26
        • 2015-12-31
        • 1970-01-01
        • 2016-10-09
        • 2012-08-18
        • 1970-01-01
        • 2014-04-25
        • 2015-12-06
        • 2011-10-31
        相关资源
        最近更新 更多