Не открою Америки, если скажу что порой требуется выполнить некую времязатратную операцию, результат которой либо не нужен пользователю вовсе (запись в лог, удаление временных файлов и другое обслуживание сервера), либо его можно обмануть и сказать что операция выполнена успешно, а саму операцию выполнить “попозже”. Самым наверное близким всем примером такой операции можно назвать отправку почты – smtp-cессия может длится довольно долго, особенно если письмо огромное, сервер тормозной (ну да вы сами всё знаете, в реальном мире великое множество острых углов), но зачем пользователю ждать результата? Что ему делать если результат неуспешный? “Попробуйте повторить операцию позже” ? Не смешно! Рядовой пользователь на ваш рядовой ресурс не вернётся, дотошный – свяжется с вами другими способами, так что можно с чистой совестью соврать ему – мол всё путём, всё отправлено, всё доставлено, записано и всё так успешно и идеально, а в это время потихоньку начать на самом деле выполнять задачу.
Конечно если вокруг вас крутятся тысячи серверов, слово “ынтырпрайз” для вас звучит буднично, то для вас уже изобретено много-много buzz-word-ных решений с очередями сообщений, очередями задач и другими полезными решениями, но львиная доля разработчиков всё-таки создают сайты-визитки, поддерживают сайты на хостинг-планах “всё по 20 рублей”, да и вообще на мой взгляд глупо для отправки почты окружать простейший php-скрипт кучей софта вроде gearmand + расширения для работы с ним. Я же хочу показать решение “для бедных”, простое, но удобное в разработке, поддержке и отладке решение.
Итак задача – отправить письмо, не заставляя ждать пользователя.
Имеем:
list($recipient,$subject,$body) = get_vars_from_request(); include 'superpupermailer.php'; $mailer = new SuperPuperMailer($recipient,$subject,$body); if ($mailer->send()) { echo "Аллилуя! Мы сделали это, храни нас Великий Байт."; } else { echo "О нет, это случилось!!! Быть того не может...но всё же случилось - приходи, милый друг, в другой раз, а сейчас ошибка!"; }
Тут всё понятно – либо отправилось, либо не отправилось – всем приходилось видеть это с разных сторон, те кто видел это со стороны браузера нередко наблюдали не только “ошибочка вышла”, но и другие подробности вроде конкретных строк в скриптах, warning-ов и Fatal error-ов. Но мы уже выяснили ранее – во-первых пользователю совершенно по барабану что у вас произошло с сервером, а во-вторых “пробовать ещё раз” он скорее всего не будет. Поэтому код можно изменить следующим образом:
list($recipient,$subject,$body) = get_vars_from_request(); $async_job = 'send();'; if (file_put_contents('/dir/for/jobs/email.php',$async_job)) { echo "Ура! Мы сделали этот мир лучше!"; } else { echo "Увы, мир жесток и безжалостен..."; }
Что это и зачем? Где отправка почты? Каким образом письмо отправится? Больше вопросов чем ответов. Опять-таки остался else, шило поменяли на мыло? В целом да – заменили тёплое мягким, но всё-таки нет – как часто у вас заканчивается неудачей запись на диск? Чаще чем таймаут при коннекте к почтовому серверу?
Теперь о главном – зачем файл? Где отправка почты?
Этим займётся другой скрипт:
if (true === include('/dir/for/jobs/email.php')) { unlink('/dir/for/jobs/email.php'); }
Осталось вызвать этот скрипт. Например так:
... echo 'Ура! Мы сделали этот мир лучше!'; ...
Или же добавим в cron задание на вызов job.php каждый час/пять минут/каждую минуту.
Всё. Мы не заставили пользователя ждать, мы отправили сообщение, мы молодцы. А если не отправили? Отправим потом – файл-то остался!
Конечно возникает много закономерных возражений – что будет если send.php (или job.php) будет вызван одновременно двумя пользователями?
Эти вопросы надо обстоятельно решать, задачи создавать с уникальными именами, блокировать одновременный запуск скрипта job.php, в общем работы аж на целых десять минут.
Самое интересное в конце:
... $job_name = uniqid('mail_'); if (file_put_contents('/dir/for/jobs/'.$job_name.'.php',$async_job)) { echo "Ура! Мы сделали этот мир лучше!"; $job_url = '/job.php?job='.$job_name; if (false !== ($fh = @fsockopen($_SERVER['SERVER_ADDR'], $_SERVER['SERVER_PORT'], $errno, $errstr, 0.01))) { fputs($fh, "GET $job_url HTTP/1.0\r\n" . "Host: {$_SERVER['HTTP_HOST']}\r\n\r\n" ); fgets($fh,32); fclose($fh); } } else { echo "Увы, мир жесток и безжалостен..."; }
Что здесь происходит? Мы создали php-файл, при выполнении которого отправится письмо, затем подключились к веб-серверу и запросили скрипт job.php, передав ему параметром имя только что созданного файла. И тут же отключились – ведь нам не важен результат, мы уже солгали пользователю о том, что операция завершилась успешно. На всё-про всё потратили доли секунды. Дальше уже дело техники – job.php захватит lock-файл, проверить наличие файла, имя которого ему передали, выполнит его, удалит в случае успеха, а затем отпустит lock-файл. Конечно надо не забывать, что скрипт может по каким-то причинам не выполнится (почтовый сервер недоступен, или ответит ошибкой, да и мало ли какие напасти происходят в реальности), поэтому следует вызывать job.php ещё и ещё, но пользователя это уже не должно волновать – у вас его письмо сохранилось и вы его доставите, он вам верит!
Разумеется решение годится не только для отправки почты, но и как я сказал в начале – для любых действий, результат которых не нужен немедленно.
Убогий костыль. Таким людям нельзя писать на php. Никогда.
Обоснуй, если таким как ты можно писать.
Gearman, rabbitMQ
Да, да! Глубже! Быстрее! Сильнее!
Русским же по белому написал – из пушки по воробьям не стреляют! Для отправки почты не поднимают ни gearmand ни rabbitMQ. Ну точнее я думаю что не поднимают, но похоже с адекватностью в этом мире нынче туго и гостевую книгу сейчас снабжают атомным реактором, блекджеком и шлюхами… Одним словом “ынтырпрайз”.
можно еще сдобрить fastcgi_finish_request() если такой есть в наличии
Сдобрили, ага.
Автор+, таким нужно писать код!!!
Мне нравится это решение! Костыль или не костыль, поиски подобного развивает мозг. А при достаточном количестве ресурсов кролика и шестерню поставить каждый новичок сможет. А что, если у вас ресурсы ограничены и вы не можете добавлять расширения в систему?!
автору +