Rails, Heroku и неверная последовательность байтов в UTF-8

У меня есть очередь текстовых сообщений в Redis. Скажем, сообщение в redis выглядит примерно так:

"niño" 

(укажите нестандартный символ).

Приложение rails отображает очередь сообщений. Когда я тестирую локально (Rails 3.2.2, Ruby 1.9.3), все в порядке, но на кедре Heroku (Rails 3.2.2, я считаю, что есть ruby ​​1.9.2). Я получаю печально известную ошибку: ActionView::Template::Error (invalid byte sequence in UTF-8)

После прочтения и перечитывания всего, что я мог найти в Интернете, я все еще придерживаюсь того, как это исправить.

Приветствуется любая помощь или указывает на правильное направление!

изменить:

Мне удалось найти решение. Я закончил использование Iconv:

string = Iconv.iconv('UTF-8', 'ISO-8859-1', message)[0]

Ни один из предложенных ответов, которые я нашел, похоже, работает в моем случае.

Ответ 1

В Heroku, когда ваше приложение получает сообщение "niño" от Redis, оно фактически получает четыре байта:

 0x6e 0x69 0xf1 0x6f

которые, если они интерпретируются как ISO-8859-1, соответствуют символам n, i, ñ и o.

Однако ваше приложение Rails предполагает, что эти байты следует интерпретировать как UTF-8, и в какой-то момент он пытается их декодировать таким образом. Третий байт в этой последовательности, 0xf1 выглядит следующим образом:

1 1 1 1 0 0 0 1

Если вы сравниваете это с таблицей на странице Википедии, вы можете видеть, что этот байт является ведущим байтом четырехбайтового символа (он соответствует шаблону 11110xxx), и как таковой должны следовать еще три байта продолжения, которые соответствуют шаблону 10xxxxxx. Это не так, вместо этого следующий байт равен 0x6f (01101111), и поэтому это недопустимая последовательность байтов utf-8, и вы получите сообщение об ошибке.

Использование:

string = message.encode('utf-8', 'iso-8859-1')

(или эквивалент Iconv) сообщает Ruby читать message как кодировку ISO-8859-1, а затем создать эквивалентную строку в кодировке UTF-8, которую вы можете использовать без проблем. (Альтернативой может быть использование force_encoding, чтобы сообщить Ruby правильную кодировку строки, но это может вызвать проблемы позже, когда вы пытаетесь смешивать UTF-8 и ISO-8859-1).

В UTF-8 строка "niño" соответствует байтам:

0x6e 0x69 0xc3 0xb1 0x6f

Обратите внимание, что первый, второй и последний байты совпадают. Символ ñ кодируется как два байта 0xc3 0xb1. Если вы напишете их в двоичном формате и сравните с таблицей в статье Wikipedia, вы увидите, что они кодируют 0xf1, что является кодировкой ISO-8859-1 ñ (так как первые 256 кодов Unicode соответствуют ISO-8859- 1).

Если вы берете эти пять байтов и считаете их ISO-8859-1, они соответствуют строке

niño

Глядя на кодировку ISO-8859-1, 0xc3 отображается на Â, а 0xb1 отображается на ±.

Итак, что происходит на вашей локальной машине, так это то, что ваше приложение получает пять байтов 0x6e 0x69 0xc3 0xb1 0x6f от Redis, что является представлением UNF-8 "niño" . На Heroku он получает четыре байта 0x6e 0x69 0xf1 0x6f, который является представлением ISO-8859-1.

Реальное решение вашей проблемы будет состоять в том, чтобы строки, помещенные в Redis, были уже UTF-8 (или, по крайней мере, все одинаковой кодировкой). Я не использовал Redis, но из того, что я могу сказать из краткого Google, он не относится к строковым кодировкам, а просто возвращает все байты, которые он дал. Вы должны посмотреть, какой процесс помещает данные в Redis, и убедиться, что он правильно обрабатывает кодировку.