1

我有一个 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 读取数据并得到不正确的响应,甚至不知道为什么。

4

1 回答 1

1

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

Content-Disposition: attachment; filename=error.csv

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

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

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

  • 根据我的实验,如果 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!. 在这种情况下,您必须提供正确的正文和标头作为调用本身的一部分。我将把上述代码的相关部分提取到可重用的方法中,作为读者的练习。

于 2014-06-26T09:57:30.237 回答