为什么 Ruby Base64 编码转换会丢失数据,我该如何路由?

Why is Ruby Base64 encoding conversion losing data and how I can route around?

我正在旋转一个 Rails UI 与葡萄 API 对话。这是该程序的第二个实例。第一个实例运行良好。然而,第二个实例的 Grape API 似乎在通过网络发送之前破坏了数据。

我需要从文件 > json > http > db 获取图像。现在我通过发送文件来做到这一点:文件 > 字符串 > 编码到 url-safe base64 > to_json > http > 解码 > 使用 ActiveRecord 保存到 sqlite3 db。根据以下内容,我将图像数据转换为 base64,导致我相信图像数据已损坏。但是,由于 Grape 都是 JSON,因此必须在发送之前对字符进行编码(因为,至少就 Ruby 的 JSON 库而言,无效的 UTF-8 ==无效 JSON).

所以我要么必须知道:

  1. 如何让 Grape API 发送非 JSON(原始文件字符串)或
  2. 如何解码字符串并避免错误消息

打开文件并将其内容转换为url-安全的 Base64。

File.open("#{folder}/#{file_name}", "rb:UTF-8") do |image|
  file_as_string = image.read
end
 => "iVBORw0K ... # truncated for length

事情马上变得很奇怪。 IRB 执行预期的操作 - 编码为 UTF-8。

file_as_string.encoding.name
 => "UTF-8"

但是。服务器记录 ASCII-8BIT。我无法解释这一点。每个文件的顶部都有 Ruby 神奇的 UTF-8 注释。 Linux $LANG 设置为 en_US.UTF-8.

好的,但是当 Base64 转换时我无论如何都失去了情节。即使在 IRB 中,从 UTF-8 开始,它也会隐蔽。为什么是 US-ASCII?无论如何,为什么兼容性会丢失?

Base64.urlsafe_encode64(file_as_string).encoding.name
 => "US-ASCII"
Base64.urlsafe_decode64(Base64.urlsafe_encode64(file_as_string)).encoding.name
 => "ASCII-8BIT"
Base64.urlsafe_decode64(Base64.urlsafe_encode64(file_as_string)).encode("UTF-8")
Encoding::UndefinedConversionError: "\x89" from ASCII-8BIT to UTF-8
    from (irb):27:in `encode'
    from (irb):27
    from /home/me/.rvm/rubies/ruby-2.2.1/bin/irb:11:in `<main>'

请注意,IRB 中的错误与我 a) 在 Grape 尝试 to_json 之前不对字符串进行 base64 编码和 b) 当我尝试解码并调用 .save 时相同Rails 侧模型属性的字符串。

文件本身是二进制的(如果这很重要?)

$ file -bi /path/to/file.png
image/png; charset=binary

我尝试过或不愿意尝试的解决方案:

发送原始文件image.read

这是一个 JSON API,因此 Grape 在通过网络发送数据之前转换为 JSON——这意味着任何响应都必须有效 JSON,因为据我了解。如果我尝试发送原始字符串,自动调用的 .to_json 会抛出相同的错误。

对结果进行强制编码

输出不是可读的 png。

降级

原始实例是 Ruby 1.9.2 和 CentOS 6.3。新实例是 Ruby 2.2.1 和 CentOS 7。我通常致力于向前发展,所以我宁愿开发一些解决方案,即使不向后兼容,然后回滚 Ruby 和我的 OS.

未使用 UTF-8

Rails 的 config/application.rb 有行 config.encoding = "utf-8"config/environment.rb 有行 Encoding.default_external = Encoding::UTF_8; Encoding.default_internal = Encoding::UTF_8 我希望不要放弃 UTF-8仅针对这一问题的兼容性。


那么有没有办法绕过 to_json 调用直接在 Grape 中提供文件?或者对于 JSON-ing 和通过 http 发送是否有不同的编码安全?

PNG 文件没有字符编码。您应该在不声明字符编码的情况下打开文件。 base64编码后也无需关心字符集

文件经过 base64 编码后,结果为 7 位 ASCII 字符串,因此 encoding.name 报告 "US-ASCII"。这是您应该传递给您的框架的字符串,

不要在 base64 编码之前对字符串调用 .encode() - 这肯定会损坏字符串。

澄清一下:

  1. file_as_string 既不是 UTF-8,也不是 ASCII。它没有字符编码,因为它是二进制文件。 file_as_string.encoding.name与你无关。
  2. Base64.urlsafe_encode64(file_as_string).encoding.name = "US-ASCII" 是正确的,因为您已经通过将二进制文件编码为 base64 将其有效地制作成 text/character 字符串。 有字符编码 - 7 位 ASCII。这就是您应该传递给 Grape 以进行传输的内容。
  3. Base64.urlsafe_decode64(Base64.urlsafe_encode64(file_as_string)).encoding.name 是无关紧要的,因为结果又是一个 binary 字符串。 它没有字符编码。尝试 .encode() 这会损坏数据。
  4. 您的 IRB 失败是因为您要求 Ruby 将二进制字符串转换为 UTF-8 文本编码。这就像拍照并要求将其转换为法语。