Pull to refresh

Comments 45

Чего только люди не придумают, чтобы комет-сервер не использовать.
Ну да — давайте поставим комет и будем его поддерживать вместо того, чтобы просто в HTTP заголовке передать что нам нужно.
$work = @$_GET['work'] 

Берет за душу, поправьте, пожалуйста на
$work = isset($_GET['work']) ?  $_GET['work'] : 0;
Еще лучше на:
$work = isset($_GET['work']) ? intval($_GET['work']) : 0;
Еще лучше на:

$work = filter_has_var(INPUT_GET, 'work') ? filter_input(INPUT_GET, 'work', FILTER_VALIDATE_INT) : 0
А подскажите пожалуйста, в чем приемущество filter_has_var над $_GET?
filter_has_var — функция, проверяющая на наличие требуемого ключа в массивах входных параметров скрипта. $_GET — один из таких массивов. В чем преимущество функции над массивом, затрудняюсь ответить…

Преимущество filter_has_var(INPUT_GET, 'work') над isset($_GET['work']) в том, что filter_has_var() вернет false, если ключ «work» был задан программно, а не передан в качестве параметра запроса. То есть:
<?php
$_GET['test'] = 1;
echo filter_has_var(INPUT_GET, 'test') ? 'Yes' : 'No';
?>
выдаст «No», если в запросе к скрипту не будет ?test=some_value.
UFO just landed and posted this here
UFO just landed and posted this here
Ваша реализация сильно зависит от настроек конкретного веб-сервера, где устанавливаются размеры буферов ответа, причем это может быть даже в нескольких местах — не хочу вдаваться в подробности.
Более «серверонезависимым» будет вариант когда клиент запускает одним ajax-запросом «тяжелый» php скрипт (назовем его process.php) и с помощью другого ajax-запроса в цикле проверяет прогресс, запрашивая второй php скрипт (назовем его status.php). Во время своей работы process.php с помощью curl (или любым другим способом) сообщает status.php свое состояние используя любой удобный вариант идентификации.
Такой вариант мне кажется намного надежнее, чем «борьба» с буферами ответа.
Такой вариант я тоже рассматривал, но мне сначала не понравилось, что параллельный ajax-запрос будет висеть на таймере. Еще не понравилось, что нужен какой-то механизм хранения состояния выполнения конкретного {process.php} (он может быть запущен одновременно несколькими клиентами несколько раз). То есть, к моменту начала выполнения нужно знать идентификатор процесса {process.php} для каждого XHR. Для каждого такого идентификатора нужно где-то хранить состояние (memcache?).
В виду этих мелочей мне и захотелось сделать так, как я сделал.
Да, с конфигурацией сервера и прилежащих частей есть загвоздки. Но большинство из них — те, которые я описал — не очень сильно преобразуют дефолтный рабочий конфиг.
Вам достаточно первый раз отдавая страницу launcher.html клиенту проставить в ней уникальный ID для процесса и использовать его при запуске процесса и при запросе статуса, например прямо в URL: /process.php?id=ID и /status.php?id=ID, свой статус process.php сообщает в status.php используя этот же ID, а это состояние status.php может хранить в своей сессии также используя этот же ID. Причем передавать можно гораздо больше информации о состоянии процесса, ну там текущий шаг к примеру и сколько осталось по этому шагу и т.д. + ничего в настройках сервера и php связанного с буферизацией вывода менять не придется. В вашем варианте надо всегда настраивать конкретный сервер, а это далеко не всегда можно легко и просто сделать.
>> /process.php?id=ID и /status.php?id=ID

По-моему проще просто в сессию все складывать, process в сессию пишет прогресс, а status оттуда его забирает
Не проще. В разных реализациях-настройках рнр сессий на различных веб-серверах возможны проблемы блокировки сессий в такой ситуации, и это надо будет как-то «разруливать» так как: Session data is usually stored after your script terminated.
А, пардон. Я уж и забыл, что в php сессии блокируются.
Я же не говорил, что это невозможно, а просто сказал, что этот метод мне показался не слишком красивым в моем случае.
Согласен, что схема, описанная вами, хороша сама по себе. Именно о таком методе почти все статьи. Я просто предложил еще один вариант (на мой взгляд, имеющий право на жизнь), о котором не слышал и не видел раньше. Среди преимуществ моего варианта: всего один XHR вместо нескольких и отсутствие таймера.
Пользователь не сможет воспользоваться вашим скриптом имея плохое соединение, например из Китая или с мобильного интернета в деревне.
Ну, при подобных обстоятельствах и запрашивать у сервера статус с интервалом — тоже будет проходить не без неприятностей.
Я понял свою ошибку: я не указал область применения. Последний случай был таким: у меня была выборка (unbuffered query) из большой таблицы БД, а в процессе транспортировки данных проводилась дополнительная фильтрация, при которой примерно треть отсеивалась. После этого проводилась сравнительно быстрая шлифовка результата. Все, результат выводился. Весь процесс занимал от полминуты до получаса (и это считалось нормой, это для админки). То есть, да, отчасти и по этой причине я даже не рассматривал post-запросы в скрипте, как бы намекая, что get — он для того и get, только для получения, прерывание которого не приведёт к проблемам вроде «скрипт отработал только на половину».
Приходилось прибегнуть к похожему решению. Главное не использовать сессии в скриптах: ajax.php и progress.php (это так: для справки).
Старт сессии устанавливает блок.
Если не закрыть сессию вы не сможете выполнить оба запроса параллельно.
1Кб на прием-передачу данных раз в 30 секунд — это много? Вы можете вычислить примерную скорость уже после 1 ответа сервера — с процентом выполнения. И дальше ее корректировать, а на клиенте показывать плавный индикатор. Там Google PageSpeed Insights делает. Вам точные проценты не нужны, можно просто реперные точки передавать.
Если юзер вдруг обновит страницу или произойдет отключение, то скрипт прекратить свою работу на половине задачи и возможно все поломается.

Я бы использовал ignore_user_abort и заставил скрипт выводить в файл % выполнения. А на странице подгружал бы этот файл по таймауту, запросы на обычные файлы у сервера проходят в разы легче чем к скриптам.
Вас не смущает, что весь этот файловый мусор потом надо будет как-то убирать?
Смотря что за задача, если требуется 1 процесс, то будет 1 файл. Если нет, можно удалять файл самим же скриптом, а на клиенте прописать, что если файл был, а потом удалился, значит задача выполнена на 100%. Или передавать доп запрос в конце на удаление файла.

Или же реализовать на скриптах, хранить в базе\памяти, зависит от задачи, требований к нагрузкам, времени которое можно уделить на решение.
Для разных ситуаций — разные решения. А если как раз нужно остановить выполнение скрипта?
Если это для обычных пользователей, то они не догадаются, что остановка загрузки страницы = остановка программы. Многие даже не знают как остановить загрузку страницы, это из опыта.

Кнопка «Стоп» передающая запрос в любом случаи будет лучше.

Можно комментировать раз в 5 минут — несправедливость.
Существует метод XMLHttpRequest.abort(), который я пробросил в своем этом классе-обертке. В примере это есть: кнопка старта играет и роль кнопки для остановки.
Websockets вам в помощь. Как раз для коротких асинхронных сообщений с сервера.
Более высокая сложность реализации, доп ПО. А из плюсов небольшая экономия на заголовках пакетов и чуть более плавная индикация состояния. В данном случаи того не стоит, как по мне.
readyState == 3

Насколько это стало кроссбраузерно?
Года 3 назад я заметил эту возможность у xmlhttprequest обрабатывать порции данных, но тогда именно вот этот ready state 2 не выдавали какие-то из популярных на то время браузеров.
Уверены, что дело в самих браузерах было?

Еще дополнение. Уже после публикации статьи я наткнулся на ситуацию в Хроме: событие onreadystatechange отрабатывало должным образом, только если Content-Type != text/plain. Если text/plain, то Хром, по видимому, складывал все в какой-то свой буфер, а уже после EOF показывал все полученные данные разом. Даже если поменять на text/unknown или text/something, проблема улетучивалась.
UFO just landed and posted this here
… Потом мы понимаем, что в файл писать слишком накладно, да и задачу нужно разпаралелить и начинаем писать в какой-нибудь MySQL. А когда база данных начнет отказывать по timeout, мы перепишем все на Redis или запихнем таблицу в память целиком. А протом окажется что нам нужна бóльшая стабильность, задачи нужно балансировать на несколько серверов, а на одной странице может выполнятся сразу несолько задач, и мы наконец сделаем нормальное решение на базе message broker и websockets.
UFO just landed and posted this here
Как правило технологии осиливаются на своем горьком опыте, так что все ок, но за использоваине файлов я все-таки влеплю вам мысленный подзатыльник. Подумайте сами, что расточительнее по ресурсам: отправить 5000 байт клиенту или сделать запись в файл 5000 раз с полным сбросом буфера. HDD начнут взлетать от такой нагрузки, а SSD вы просто задолбаетесь менять.
Если это нужно для пары несложных страниц на всю систему (которая, как часто бывает, живет под девизом «нужно приспосабливаться под текущие условия — так сложилось исторически»), то все эти домыслы не к месту.
Мне кажется все зависит от того что за долгий скрипт собирает выполнятся. Я бы делал на ReactPHP (WebSokets).
Простите конечно, но хабрахабр не Stackoverflow. А ваша статья больше похожа на тот как делать не стоит, даже несмотря на просьбу сильно не пинать в конце.

Кроме того, то ли Nginx, то ли FastCGI, то ли сам Chrome считают, что инициировать прием-передачу тела ответа, которое содержит всего-навсего один байт — слишком расточительно. Поэтому нужно предварить всю операцию дополнительными байтами. Нужно договориться, скажем, что первые 20 пробелов вообще ничего не должны означать.


Можно бесконечно смотреть на огонь, воду и то как PHP-разработчики решают проблемы. Вы сделали очень грязный хак для вашей пары браузер-вебсервер, но вы точно не решили проблему, ведь в даже не знаете, что ее вызвало. Что если в каком-то другом случае 20ти пробелов недостаточно?
Присоединяюсь и добавлю, что хак в 20 байтов связан с наполнителем буфера, который помимо прочего содержит еще и заголовки сервера. т.е. мало того, что буфер у всех разный, так еще всё будет зависеть от количества заголовков. Автор, проведите эксперимент: добавьте еще пару лишних хэдеров, и посмотрите, нужны ли 20 пробелов. Результат в студию
Не знаю — я так и написал.
Эксперимент с дополнительными хедерами провалился: они ничего не дают.

onreadystatechange срабатывает, но в responseText — пусто. Срабатывает столько раз, сколько пришло байт, но тело ответа пустое, а потом, когда тело становится длиннее волшебного числа 8 (в моем случае на самом деле не 20) — responseText моментально преобразуется в строку из 8 байт.

У меня были догадки на этот счет, которые упираются в Apache и Nagle. Но после наблюдений они разошлись с догадками и я не стал умничать и вводить читателя в заблуждение, а спросил.

Быть может, у вас есть точный ответ?
Точного ответа у меня нет, я могу только гадать. 8 байт натолкнули меня на мыcль: может стоит указать в хедерах явно кодировку ascii. Еще попробуйте поставить `Content-Type: application/octet-stream`.
Sign up to leave a comment.

Articles