Изменение формата сессии при переходе с Rails 3.0 на 3.2
Групон сейчас активно переводится с rails 3.0 на rails 3.2. Самая досадная неожиданность, которая встретилась в процессе переезда — это изменение формата хранения flash сообщений в сессии. Если бы при этом flash сообщение пропадало, то это было бы не страшно, но при декодировании сессии происходит исключение, и пользователь видит 500 ошибку до тех пор, пока не почистит куки. Конечно, пользователей, у которых в сессии будет flash сообщение в момент переключения с версии 3.0 на 3.2 будет не так и много, но это пользователи, потерянные навсегда.
Об этом даже завели баг в трекере на гитхабе. Jose Valim сделал по
этому поводу коммит, в котором
добавил комментарий к классу FlashHash
с просьбой не менять сигнатуру класса без надобности. Также в трекере
предлагали поменять ключ сессии, то есть разлогинить всех пользователей с сайта. Я не знаю, можно ли будет потом
объяснить такой поступок нашему отделу продаж, потому что то, что они заметно упадут, проверено на опыте.
Поэтому я пошел дальше, и решил поправить эту ошибку.
Изучение того, как работает Marshal.load
особой радости не принесло, потому что читать
код на C без хорошего знания внутренностей ruby — занятие достаточно бесполезное.
Я посмотрел, что поменялось, и достаточно быстро пришел к хорошему решению — отнаследовать класс FlashHash
от Hash
.
# config/initializers/session_hack.rb
module ActionDispatch
class Flash
remove_const :FlashHash # чтобы можно было полностью переопределить класс
class FlashNow < Hash
# здесь полная копия класса FlashHash из
# action_dispatch/middleware/flash.rb в геме actionpack
end
end
end
Но мне оно не очень понравилось, слишком какой-то сильный хак, который надо будет потом поддерживать и про него не забыть. Поэтому я решил, что разлогинить пользователей, у которых есть flash сообщение в сессии — это не очень большой грех (а их ведь мало совсем), поэтому поступил вот так.
# config/initializers/session_hack.rb
module ActiveSupport
class MessageVerifier
def compat_verify(signed_message)
raise InvalidSignature if signed_message.blank?
data, digest = signed_message.split("--")
if data.present? && digest.present? && secure_compare(digest, generate_digest(data))
result = Marshal.load(Base64.decode64(data)) rescue {} # а вот здесь все валилось
else
raise InvalidSignature
end
end
end
end
Неясно в этой истории только одно. Зачем в фреймворке, который используется „в бою“ на огромном количестве систем, вносить
несовместимые изменения, которые можно было бы не вносить. Учитывая то, что с flash сообщениями уже были проблемы при
переходе с 2.3 на 3.0, я думаю, может быть стоит по-другому сериализовать данные в сессию, потому что загадочный Marhsal
с его „шикарным“ ArgumentError
иногда вызывает недоумение.