Tips & Tricks: PHP, Rails, Nginx, Apache, Passanger, MySQL, PgSQL, RabbitMQ, Memcached, …

В один прекрасный момент приложение на нашем production-сервере (написанное на Ruby On Rails), начало докучать нам не страшными, но противными Exception’ами. Выглядело это так:

CollectionsController# (ActionView::MissingTemplate) “Missing template collections/show, application/show with {:handlers=>[:erb, :builder, :haml], :formats=>[\"*/*;q=0.9\"], :locale=>[:en, :en]}.

В ходе расследования было выяснено, что такие запросы слал бот со следующим User-agent:

DoCoMo/2.0 N905i(c100;TB;W24H16) (compatible; Googlebot-Mobile/2.1; +http://www.google.com/bot.html)

и HTTP-заголовком “Accept */*;q=0.9″. Как известно в заголовок Accept можно выставлять несколько mime-types, добавляя к ним параметр q (quality), который указывает веб-серверу “предпочтительность” одного типа перед другими. Впрочем, об этом лучше почитать в соотвествующем RFC. Поэтому вызвало недоумение наличие параметра q вместе с только одним mime-type. Хоть этот параметр и бессмысленен, но RFC 2616 ничего не говорит о таком случае. Оставим на совести google-бота такое непонятное поведение. Так или иначе, с этим нужно было что-то делать.

В недрах Rails был найден следующий код, отвечающий за разбор заголовка Accept в actionpack/lib/action_dispatch/http/mime_type.rb (актуально для ветки 3.1):

  1. def parse(accept_header)
  2.   if accept_header !~ /,/
  3.     if accept_header =~ TRAILING_STAR_REGEXP
  4.       parse_data_with_trailing_star($1)
  5.     else
  6.       [Mime::Type.lookup(accept_header)]
  7.     end
  8.   else
  9.     # keep track of creation order to keep the subsequent sort stable
  10.     list, index = [], 0
  11.     accept_header.split(/,/).each do |header|
  12.       params, q = header.split(/;\s*q=/)

Как видно split для header’a делается только в случае если майм-тайпов было несколько (accept_header !~ /,/), в противном выполнялось слудующее: Mime::Type.lookup(“*/*;q=0.9″). В этом-то и проблема. Не похоже, чтобы эта проблема сильно докучала общественность, но тем был найден тикет на Rails lighthouse более чем годичной давности с патчем, но его как видно не приняли. Было решено написать pull-request, впрочем тоже проигнорированный на данный момент(его можно посмотреть и проголосовать здесь). Нужено было искать какой-нибудь workaround, в ходе которого родился следующий middleware: https://github.com/uzzz/rails-accept-header-fix.

 

P.S. Воспроизвести можно с помощью:

curl -H "Accept:*/*;q=0.9" -I "http://example.com"

Макс Жилинский

Maksim Zhylinski

Twitter