Нет, это не в глазах двоится
Разумеется, первое что пришло в голову — где-то в скрипте «двоится» запрос к базе данных. Например, такая «школьная ошибка»:
$query = "INSERT INTO table VALUES(...)";
$result = mysqli->query($query);
if ($result = mysqli->query($query)) {
...
}
Проверяю сто тысяч раз — нет. С кодом все в порядке. Значит браузер по какой-то причине отправляет данные дважды. Генерирую простенький тестовый скрипт с инкрементом сессионной переменной и — да! Вместо увеличения на единицу браузер упорно показывает увеличение переменной на два. Пробую вместо Хрома Сафари — нет такой проблемы. Далее идут поиски некорректно работающих яваскриптов, расширений для браузера, но все безрезультатно. Поиск по интернету давал схожие советы. Многие программисты в аналогичной ситуации вводили проверку на уникальность и отсекали дублирующиеся посты. Но баг то это решение не устраняет и я решил не останавливаться и найти причину такого поведения.
Наконец, причина была найдена. И так как решение нашел с трудом, на англоязычной девелоперской ветке, хочу поделиться им здесь. Все просто — виновника два: Google Chrome (и производные от него браузеры) и mod_rewrite в Apache.
Суть проблемы
Все просто. Особенность браузеров, построенных на платформе Crome в том, что они ищут файл favicon.ico для каждого сайта. И если его нет, то они все равно будут упорно его искать. При каждом обновлении страницы, отдельным запросом к серверу. А большинство .htaccess файлов в Apache имеют в себе строчки:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . /index.php
Руководствуясь этим правилом, получая запрос от браузера к несуществующему favicon.ico Apache послушно перенаправит его к файлу index.php и скрипт отработает дважды. Конечно, в большинстве случаев полноценные веб-приложения имеют проверку на уникальность и не пропускают повторяющиеся запросы. А сайты в большинстве случаев имеют файл favicon.ico. Но все же раз существует такая проблема, значит можно описать методы решения.
Варианты решения
Решение первое, самое простое: заведите на сайте favicon.ico. Chrome найдет его и успокоится.
Решение второе — немного изменить файл .htaccess на сервере. У меня блок правил mod_rewrite для всех проектов теперь будет выглядеть так:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
# Next line is to solve the Chrome and favicon.ico file issue.
# Without it browser sends two requests to script.
RewriteCond %{REQUEST_FILENAME} !favicon.ico
# RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
Не поставил favicon? Получи двойной трафик от Chrome
Если вдуматься, то утверждение справедливо, ведь вместо того, чтобы отдать «статику», сервер будет запускать веб-приложение, которое в лучшем случае вернёт в браузер вместо иконки динамически сгенерированную 404 страницу. А в худшем — отработает полностью запуск главной страницы сайта. Получается, что не установив иконку на сайте (например, на одном популярном блоговом движке), разработчик вдвое увеличивает нагрузку на сервер от пользователей Google Chrome.
Вот, собственно и все. Надеюсь эта информация кому-то пригодится и сэкономит время.
Комментарии (59)
saluev
07.09.2016 17:20+26Мне кажется, статья несколько неполная. Например, заголовок я бы дополнил так:
Не поставил favicon на сайте, халтурно сокнфигурировал Apache и по-дурацки организовал работу скриптов и БД — получи двойной трафик от Chrome
А одно из предложений в тексте так:
… большинство .htaccess файлов в Apache, которые я написал для своих сайтов, имеют в себе строчки...
pda0
07.09.2016 17:24+10Не, двойной трафик от Chrome, а вот двойной INSERT уже от собственных кривых рук.
mmman
15.09.2016 10:51Это в общем-то стандартный конфиг Wordpress, на котором крутится до 25% сайтов в мире.
ErickSkrauch
07.09.2016 17:26+20Не совсем понимаю: запрос на favicon делает через GET. Вставка данных, в современном мире, делает через POST. Мне кажется, автор сам себе прострелил ногу.
sad
07.09.2016 17:33Может у них там статистика какая-то своя считается по заходам на страницу, например.
ErickSkrauch
07.09.2016 17:34И по загрузке статики?
wispoz
07.09.2016 17:36+1Если все запросы заворачиваются на Index.php то да, халтурно настроенный апач) в вверху же написали.
pudovMaxim
07.09.2016 17:26+6У меня лыжи не едут.
Почему две вставки происходит? Хром обратится GET-запросом напрямую к /favicon.ico. Никаких данных в POST или GET быть не должно. С чего вдруг двойное выполнение insert? Тогда получается любой 404ый запрос с этой страницы может косячить, даже аякс?FeNUMe
07.09.2016 20:57+1Автор кривым .htaccess завернул все 404(включая статику) на index.php в итоге получил многократное выполнение основной логики.
TrogWarZ
07.09.2016 17:58+5> Внезапно я обнаружил, что на новом сайте, который у меня сейчас в разработке на локалхосте, дублируются INSERT запросы к БД. Отправляю один комментарий через форму, а в базу вставляются два.
Реквестирую подробности рассказа о том как комментарии в базу вставляются по GET-запросу да ещё и без дополнительных данных.
А вообще, нагрузку увеличить Хром и правда может не только как тут в комментах уже писали о user-friendly 404, но и при наборе в адресной строке конкретного адреса: prefetch-логика шлёт два GET-запроса, что дважды загружает страницу, инкрементирует дважды счётчики, даже может сбросить выделение непрочитанных данных в некоторых случаях.
andreymal
07.09.2016 17:58+5Помимо вышенаписанного, это что же получается — автор перед тем, как отправиться гуглить, даже не заглянул в access.log? Ну или в мониторинг сети в самом хроме, в конце-то концов.
el777
07.09.2016 18:03+11Все верно: сделал говносайт через ж… — получи траф.
Вы мне лучше расскажите, почему попадание на несуществующую страницу увеличивает счетчик заходов? Пользователь никуда не попал (404 == страницы нет)? Какая в этом логика подсчета? Хотите редирект — ок, перенаправьте и там засчитайте.
Завтра к вам зайдут с iPhone/iPad и он запросит картинку apple-touch-icon.png. Еще строчку добавите в .htaccess?
Вот где проблема: вы не хотите исправить кривое решение, а хотите обставить костылями по кругу.
Жду еще 20 статей такого же рода, про каждый новый тип картинки.
medved6216
07.09.2016 19:22+11 апреля уже? Пятница 13? Вы поржать или попугать? :) Без исходников вашего велосипеда(«движка») и настроек апача(.htaccess), тщательно проведенного анализа — кто сюда это пропустил?
А по делу:
1. плохо настроен modrewrite (10%);
2. Ошибка в написании велосипеда, а именно в обработке «404 Not found» — скорее при обработке передаются все параметры запроса и если автор обрабатывает параметры в одном месть, то параметр на событие повторяется (например, index.php?act=comment) — не обязательно get, скорее всего запрос обрабатывается, через Request.
handicraftsman
07.09.2016 20:36В firefox почти такая-же проблема. Там дублируется запрос лишь к favicon.ico.
handicraftsman
15.09.2016 12:03Интересно, что проблема эта — кроссерверная. Обнаруживается даже на моём самописном сервере.
tonissimo
07.09.2016 20:46Помимо всего вышеописанного, стоит добавить, что заголовки Last-Modified, ETag, Cache-Control и Expires — для зануд, пусть поисковые роботы сами проверяют когда у меня контент обновится.
Lure_of_Chaos
07.09.2016 21:02+5Ждем серию постов от ТС о проблемах, возникших после того, как сайт стал виден из WWW…
LeonidZ
07.09.2016 23:35+3Меня больше потрясло то, что вы по сути по любому урлу без проверки, что именно вообще было запрошено, делаете инсерт в базу! Просто клондайк для DDOS. А как же css, js и другая статика?
Статья должна называться: «как я несколько лет косячил с настройками apache и не проверял входящие данные в PHP».
Я открою вам секрет: не только в Chrome так. Многие браузеры во время совершения ajax запроса шлют сначала OPTION запрос для принятия заголовков Allowed-*, например.
Пересмотрите свое отношение к написанию кода, я прямо чувствую там дуршлаг )
kamilsk
08.09.2016 10:44+2Не поставил favicon на сайте — написал статью на хабре
Я бы посоветовал почитать книжки про сетевой стек, поизучать как запросы вообще приходят на сервер, используя протокол HTTP, как можно эти запросы анализировать и делать выводы, а не «Далее идут поиски некорректно работающих яваскриптов, расширений для браузера, но все безрезультатно. Поиск по интернету давал схожие советы. Многие программисты в аналогичной ситуации вводили проверку на уникальность и отсекали дублирующиеся посты.»
Еще в Developer Tools есть замечательная вкладка Network, где можно посмотреть все запросы, которые отправляет браузер
AleXP3
08.09.2016 10:44+1Интересно: вы любой запрошенный файл, который не нашелся на вашем сервере, планируете в .htaccess вносить?
webmoder
08.09.2016 10:51Соглашусь с комментаторами выше, почему на 404 GET запросе происходит insert?
Если вы пишите свою систему статистики посещений, то:
- во-первых не стоит записывать каждый HTTP запрос (Это уже не статистика а логгер, который легко настроить на HTTP сервере).
- во-вторых исключите обработку своим index.php запросы на статику по паттернам (прим. /assets/*, /favicon.ico, /robots.txt, /sitemap.xml), даже если файлов таких нет, в дальнейшем вы избежите подобных проблем.
- в-третьих никакого двойного трафика нет, у вас так-же происходят запросы на получение js, css и т.п
.З.Ы. Помню была немного похожая ситуация с хромом:
Писал платежные шлюзы которые тестировались через GET запрос аля:http://{ip}/gatewayName.php?params....
через какое-то время обнаружил что один шлюз начал переодически самопроизвольно запускаться, после долгого исследования обнаружил что хром добавил url в стартовую панель и переодически пытался обновить preview страницы.
IlyaMoiseev
08.09.2016 11:06Коллеги, спасибо за то, что уделили внимание и обсудили мою статью. Спасибо за все плюсы и минусы. Некоторые комментарии оказались действительно полезными — я не считаю себя «гуру» программирования и стараюсь развиваться.
На язвительные комментарии отвечу, что речь в статье шла о необычном поведении браузера Chrome (другие браузеры не особо докапываются до отсутствия иконки) и о том, к чему это может привести в случае некорректно настроенного .htaccess на сервере. (А он такой по дефолту во многих open source движках, например Wordpress, так что думаю, проблема может оказаться распространенной). Например, я бы ожидал столкнуться с отсутствующими файлами в upload директории сайта, ну или в папке с темой оформления, но забыл бы про корень сайта. Статья никоим образом не должна иллюстрировать грамотные подходы к созданию современных сайтов.
А про SELECT — тестировал новый метод класса на локалхосте. Сознательно вбил его вызов напрямую в index.php и в браузере F5, F5, F5… Только ради теста. Не надо придавать этому значения.
pudovMaxim
08.09.2016 12:05Кстати, это уже было (в сиспсонах) на хабре: https://habrahabr.ru/post/140693/
LeamasRein
08.09.2016 12:52-1Не буду разводить на холиваров на тему апача в 2к16, лампа на локалке дело обычное, но действительно пугают следующие вещи:
— конфиг mod_rewrite;
— INSERT в БД по GET запросу;
— INSERT в БД без CSRF;
— определение причинно-следственной связи.
Школьнику/студенту еще простительно, но 5 лет разработки в вебе?
ps. Почитал комментарии. Товарищам, считающим, что ТС добавлял записи для счетчика просмотров — прошу прочитать первый абзац в статье чуточку внимательнее, где явно указано про форму комментариев. Хотя и с прямым взаимодействием (вставка/обновление) с БД при реализации счетчика просмотров я бы поспорил.
G-M-A-X
08.09.2016 22:02Тема
сисекпочему по другому адресу по другому методу добавлялись комменты не раскрыта.
А конфиг-то дефолтный, наверное, для большинства систем с единой точкой входа :)
elfiki
Тут скорее отсутсвие обработки запросов, которые должны 404 страницу возвращать, а не отсутствие фавикона.
Ну, т.е. странно что обращение к "/favicon.ico" и "/comment/" какой-нибудь обрабатываются одинаково
AndreyRubankov
Во-первых, есть кейс, когда пользователя с 404 лучше отправить на home page, чтобы он не ушел. Во-вторых, иногда даже 404 страница должна быть не просто статикой (редко, но бывает).
В результате любой 404 может превратиться в нагрузку на сервер.
Проблема для продакшена не частая — почти любой сайт имеет favicon, но тем не менее.
elfiki
Ну ок, в случае когда идет отправка на главную — то получится редирект и дополнительный показ главной страницы, но никак не добавление комментария. Ну ок, в случае когда 404 не просто статика, то все-равно это страница 404, а не добавление комментария в базу.
AndreyRubankov
Думаю, автор пример добавления в базу добавил просто для того, чтобы показать «ААА, Мы все умрем!», и судя по-всему, его за это и минусуют.
Но дополнительный запрос на главную с поднятием и выполнением кучи кода, иногда даже с хождением в базу будет.
DistortNeo
Ну так делайте динамический переход с помощью JavaScript.
AndreyRubankov
Иногда js в браузере может быть выключен и иногда все же есть требования, чтобы сайты работали без js. Да, это не можно, не круто, но иногда бизнесу так выгоднее. А соответственно js редирект не будет работать.
А еще есть прикол с Referer при редиректе через js (иногда это тоже важно). При серверном редиректе referer нового запроса будет первоначальная страница, а при js редиректе — текущая страница.
DistortNeo
Для этого существует тэг noscript. Включены скрипты — пользователь видит пустую страницу, с которой тут же идёт переход. Выключены скрипты — пользователь получает простую статическую страницу.
Это случится, если при некорректном запросе сначала делать редирект на страницу 404, а не отдавать код сразу.
AndreyRubankov
> Для этого существует тэг noscript.
— пользователь пришел на 404.html и ушел, это не то, что он ожидал увидеть и ничего, что привлекло бы его внимание тут нету. пользователь ушел.
серверный редирект позволит сразу его перекинуть на хомпейдж, и там уже есть вероятность, что он задержится дольше, чем на 404.html
А решение проблемы с /favicon очень простое — нужно правильно настроить рулы редиректа. Для всех картинок прописать 404, без редиректа, а для 404 при запросе html — прописать редирект на хомпейдж. Велосипедов придумывать не нужно, все уже давно известно.
> Это случится, если при некорректном запросе сначала делать редирект на страницу 404, а не отдавать код сразу.
сценарий:
— пользователь из внешнего источника «А» тыкает на ссылку, которая ведет на отсутствующую страницу (почему отсутствует — другой вопрос).
страницы нету — он попадает на 404.html, на которой стоит редирект на хомпейдж. перейдя на хомпейдж — у него в referer будет 404.html, а не «ресурс А»
единственный способ сохранить referer — серверный редирект.
DistortNeo
Да, это более правильный способ.
А зачем делать редирект на 404.html, когда можно просто отдать код страницы?
Тогда в Referer будет не 404.html, а адрес некорректного запроса. Если же позарез нужен именно «ресурс A», тогда можно его передавать через параметры запроса.
Впрочем, возни с сервисами аналитики это прибавит.
AndreyRubankov
> А зачем делать редирект на 404.html, когда можно просто отдать код страницы?
Спасибо, интересный вариант, про него не подумал про него!
Но есть 2 «но»:
1. Отдав контент и далее сделав редирект — Referer у редиректа будет запрошенная страница (которой нету).
2. Этот вариант займет больше времени. Сначала нужно доставить контент (объем больше, чем у пустого редиректа), потом нужно будет этот контент отрендерить, и лишь потом будет выполнен редирект.
AleXP3
Не будет, если сайт спроектирован и настроен правильно. И вот почему:
_RFC 2616_
Случай с прямым набором с клавиатуры — частный. Но браузеры не выставляют Referer и в случаях когда напарываются на отсутствующий на сервере контент и получают в ответ от сервера код состояния 4** b редирект на другую страницу.
AndreyRubankov
Кажется вы не к тому комменту ответили. Ваши слова лишь подтверждают мои.
Моя позиция в том, что для сохранения referer нужно делать 3xx редирект html контента.
В сообщениях выше по этой ветке коллеги предлагают отдать контент и уже из контента делать редирект. Отдать html контент с 404 кодом, даже если и получится, то это не правильно.
И если таки отдать 404.html — то при редиректе с нее в реферер будет установлен 404.html, а не ресурс, с которого пришли.
Vadiok
Все-таки лучше не сразу отправлять на главную, а показать 404 и, если уж так надо, то добавить мета-тег c [http-equiv="refresh"], чтобы пользователя перекидывало на главную через несколько секунд (работает без JS):
DistortNeo
Кстати, ещё одна причина отдать именно страницу 404 — банальное следование стандартам. При некорректном запросе вы должны вернуть HTTP-ответ с кодом 4**, иначе будете вводить как браузеры, так и поисковые страницы в заблуждение.
AleXP3
Строго говоря: отдать 4** статус, но не обязательно страницу.
AleXP3
Дополнение:
Без статуса 404, на месте отсутствующего контента, даже, например, через «инструмент вэбмастера» Яндекса, убрать из поискового массива страницу не получится, сколько не натравливай робота. Так что играться со статусами, и выставлять их заведомо не правильно или забить на них вообще, это совершенно не богоугодное дело.
AndreyRubankov
Судя по минусам, мой коммент не поняли.
Запись в базу — это тупость, без сомнения. Судя по всему, автор хотел нагнать паники «Мы все умрем».
А так же бизнес кейсы бывают разные. Не все делается на фронтенде, как бы этого не хотелось многим.
zapimir
Какой Home page по 404, если речь о favicon.ico? Вы на запрос иконки собираетесь отдавать html?
Банально у автора неправильно настроена обработка путей. Это довольно частая проблема, среди тех кто любит бездумно все запросы заворачивать на index.php.
AndreyRubankov
Я не собираюсь уже поверьте.
Но вот есть множество проектов, которые сделаны на столько коряво, что и на /favicon отдается 404.html и даже на ошибочный рест запрос тоже 404.html словить можно.
mmman
Это обычное дело для систем типа Wordpress, где ЧПУ формируются автоматически. Вместо того, чтобы в htaccess описывать все возможные маски названий страниц, происходит перенаправление на index, а дальше тот сам в базе ищет соответствующий пост.