【问题标题】:How can I have Grape return error messages in CSV format?如何让 Grape 以 CSV 格式返回错误消息?
【发布时间】:2014-06-26 02:21:42
【问题描述】:

我有一个 Rails 应用程序,并且我使用 Grape gem 实现了 api。现在,我创建了一个自定义错误格式化程序 (CSVFormatter) 以 CSV 格式返回错误响应。

而且,我的应用程序的 v2.rb 文件中也有这个:

error_formatter :csv, Api::Base::Errors::CSVFormatter

当我点击这样的网址时:

http://example.com/api/v2/datasets/CODE/data.csv?&trim_start=06/01/99&trim_end=2014-05/28&sort_order=desc

它在控制台中显示这样的错误,这很好,意味着我的自定义错误格式化程序工作正常:

Error 
trim_start is invalid 
trim_end is invalid

但是,我只需要将这个错误信息下载到一个 csv 文件中。在查看了 Grape 的文档后,我找到了一种设置 Content-type 的方法,我尝试了这个:

      rack = Rack::Response.new(as_csv , 422, { "Content-type" => "text/csv" }).finish
      rack[2].body[0]

但是,这并没有像我预期的那样工作。

编辑:

根据西蒙的回答,看起来没有使用葡萄不强制覆盖状态代码的干净方法。但是,人们可能不希望这样做,因为这可能会导致应用程序中出现其他问题,例如如果其他程序试图从 api 读取数据并得到不正确的响应,甚至不知道为什么。

【问题讨论】:

    标签: ruby-on-rails grape grape-api


    【解决方案1】:

    您正在寻找the Content-Disposition header。将其包含在您的回复中,如下所示:

    Content-Disposition: attachment; filename=error.csv
    

    Web 浏览器会将响应正文视为要下载的文件(在本例中为“error.csv”)。

    但是,修改代码以执行此操作会因两件事而变得复杂:

    • the Grape source code 看来,显然无法从错误格式化程序中设置响应标头,因此您需要添加一个自定义异常处理程序来格式化响应正文并为您计划的每种输出格式适当设置响应标头支持。

    • 根据我的实验,如果 HTTP 状态码指示错误(例如 400 或 500 范围内的任何内容),浏览器将忽略 Content-Disposition 标头,因此当用户请求时也需要覆盖状态码CSV 文件。

    尝试将其添加到您的 API 类中:

    # Handle all exceptions with an error response appropriate to the requested
    # output format
    rescue_from :all do |e|
      # Edit this hash to override the HTTP response status for specific output
      # formats
      FORMAT_SPECIFIC_STATUS = {
        :csv => 200
      }
    
      # Edit this hash to add custom headers specific to each output format
      FORMAT_SPECIFIC_HEADERS = {
        :csv => {
          'Content-Disposition' => 'attachment; filename=error.csv'
        }
      }
    
      # Get the output format requested by the user
      format = env['api.format']
    
      # Set the HTTP status appropriately for the requested output format and
      # the error type
      status = FORMAT_SPECIFIC_STATUS[format] ||
                 (e.respond_to? :status) && e.status ||
                 500
    
      # Set the HTTP headers appropriately for the requested format
      headers = {
        'Content-Type' => options[:content_types][format] || 'text/plain'
      }.merge(FORMAT_SPECIFIC_HEADERS[format] || { })
    
      # Format the message body using the appropriate error formatter
      error_formatter =
        options[:error_formatters][format] || options[:default_error_formatter]
      body = error_formatter.call(e.message, nil, options, env)
    
      # Return the error response to the client in the correct format
      # with the correct HTTP headers for that format
      Rack::Response.new(body, status, headers).finish
    end
    

    现在,如果您将 API 类配置为处理两种不同的格式(为简单起见,我在此处选择了 CSV 和纯文本),如下所示:

    module Errors
      module CSVErrorFormatter
        def self.call(message, backtrace, options, env)
          as_csv = "CSV formatter:" + "\n"
          message.split(",").each do |msg|
            as_csv += msg + "\n"
          end
    
          # Note this method simply returns the response body
          as_csv
        end
      end
    
      module TextErrorFormatter
        def self.call(message, backtrace, options, env)
          as_txt = "Text formatter:" + "\n"
          message.split(",").each do |msg|
            as_txt += msg + "\n"
          end
    
          as_txt
        end
      end
    end
    
    content_type :csv, 'text/csv'
    content_type :txt, 'text/plain'
    
    error_formatter :csv, Api::Base::Errors::CSVErrorFormatter
    error_formatter :txt, Api::Base::Errors::TextErrorFormatter
    

    您应该会发现您的 API 始终返回适合所请求格式的错误响应,并且仅在请求 CSV 格式时触发浏览器下载响应。当然,可以通过显式声明内容类型和错误格式化程序来扩展它以支持尽可能多的格式。

    请注意,在这种情况下,此代码不会自动执行正确的操作,即使用error! 直接调用错误响应。在这种情况下,您必须提供正确的正文和标头作为调用本身的一部分。我将把上述代码的相关部分提取到可重用的方法中,作为读者的练习。

    【讨论】:

    • 我的回答不正确;我一直忘记 API 类永远不会被实例化,所以它的方法必须都是静态的。但我现在正在为自己尝试这个,rescue_from 的行为不像我相信的那样。我会尽快用解决方案更新我的答案。
    • 对。您的策略是正确的,除了查看 Grape 源代码外,我可以看到无法在错误格式化程序中设置响应标头。因此,如果有一个简单的解决方案,则必须在生成异常和将错误响应发送回客户端之间的路径上的某个其他点注入标头。
    • 我已经更新了我的答案——这段代码我自己测试过,它在本地运行良好。该代码是不言自明的,但如果您对此有任何疑问,请在此处发布,我会回答。
    猜你喜欢
    • 2015-08-31
    • 2020-06-12
    • 2021-04-08
    • 1970-01-01
    • 1970-01-01
    • 2020-01-23
    • 1970-01-01
    • 2016-10-06
    • 1970-01-01
    相关资源
    最近更新 更多