Как запустить PHP-скрипт в фоновом режиме после отправки формы? - PullRequest
96 голосов
/ 07 января 2011

Проблема
У меня есть форма, которая при отправке запускает базовый код для обработки отправленной информации и вставляет ее в базу данных для отображения на веб-сайте уведомлений.Кроме того, у меня есть список людей, которые подписались на получение этих уведомлений по электронной почте и SMS.Этот список на данный момент тривиален (всего около 150), однако этого достаточно, чтобы циклически просмотреть всю таблицу подписчиков и отослать более 150 электронных писем.(Электронные письма отправляются индивидуально в соответствии с запросами системных администраторов нашего почтового сервера из-за массовых почтовых политик.)

В течение этого времени лицо, разместившее предупреждение, будет сидеть на последней странице формы дляпочти минуту без какого-либо положительного подтверждения того, что их уведомление публикуется.Это приводит к другим потенциальным проблемам, все, у которых есть возможные решения, которые, я считаю, не идеальны.

  1. Во-первых, автор может подумать, что сервер отстает, и снова нажмите кнопку "Отправить"., в результате чего скрипт запускается заново или запускается дважды.Я мог бы решить эту проблему с помощью JavaScript, чтобы отключить кнопку и заменить текст, чтобы сказать что-то вроде «Обработка ...», однако это далеко не идеально, потому что пользователь все равно будет застревать на странице во время выполнения скрипта.(Кроме того, если JavaScript отключен, эта проблема по-прежнему существует.)

  2. Во-вторых, автор может преждевременно закрыть вкладку или браузер после отправки формы.Сценарий будет продолжать работать на сервере до тех пор, пока он не попытается выполнить обратную запись в браузер, однако, если пользователь затем просматривает любую страницу в нашем домене (пока сценарий еще выполняется), браузер зависает, загружая страницу, пока сценарий не завершится,(Это происходит только тогда, когда вкладка или окно браузера закрыты, а не все приложение браузера.) Тем не менее, это не идеально.

(возможно) Решение
Я решил, что хочу разбить часть скрипта на «электронную почту» на отдельный файл, который я могу вызвать после публикации уведомления.Первоначально я думал поместить это на страницу подтверждения после того, как уведомление было успешно опубликовано.Тем не менее, пользователь не будет знать, что этот скрипт запущен, и любые аномалии не будут им очевидны;Этот сценарий не может завершиться сбоем.

Но что, если я могу запустить этот сценарий в качестве фонового процесса?Итак, мой вопрос заключается в следующем: как я могу выполнить сценарий PHP для запуска в качестве фоновой службы и работать полностью независимо от того, что пользователь сделал на уровне формы?

РЕДАКТИРОВАТЬ: Это нельзя быть cron'ed.Он должен запускаться сразу после отправки формы.Это высокоприоритетные уведомления.Кроме того, системные администраторы, работающие на наших серверах, запрещают запускать кроны чаще, чем 5 минут.

Ответы [ 15 ]

81 голосов
/ 07 января 2011

Проводя некоторые эксперименты с exec и shell_exec, я нашел решение, которое отлично работало! Я решил использовать shell_exec, чтобы я мог регистрировать каждый процесс уведомления, который происходит (или нет). (shell_exec возвращается в виде строки, и это было проще, чем использовать exec, назначив вывод переменной и открыв файл для записи.)

Я использую следующую строку для вызова сценария электронной почты:

shell_exec("/path/to/php /path/to/send_notifications.php '".$post_id."' 'alert' >> /path/to/alert_log/paging.log &");

Важно отметить & в конце команды (как указано @netcoder). Эта команда UNIX запускает процесс в фоновом режиме.

Дополнительные переменные, заключенные в одинарные кавычки после пути к сценарию, задаются как $_SERVER['argv'] переменные, которые я могу вызывать в своем сценарии.

Сценарий электронной почты затем выводится в мой файл журнала, используя >>, и выводит что-то вроде этого:

[2011-01-07 11:01:26] Alert Notifications Sent for http://alerts.illinoisstate.edu/2049 (SCRIPT: 38.71 seconds)
[2011-01-07 11:01:34] CRITICAL ERROR: Alert Notifications NOT sent for http://alerts.illinoisstate.edu/2049 (SCRIPT: 23.12 seconds)
16 голосов
/ 07 января 2011

На серверах Linux / Unix вы можете выполнить задание в фоновом режиме, используя proc_open :

$descriptorspec = array(
   array('pipe', 'r'),               // stdin
   array('file', 'myfile.txt', 'a'), // stdout
   array('pipe', 'w'),               // stderr
);

$proc = proc_open('php email_script.php &', $descriptorspec, $pipes);

Важным битом здесь является &.Сценарий будет продолжен, даже если исходный сценарий завершился.

6 голосов
/ 07 января 2011

PHP exec("php script.php") может сделать это.

Из Вручную :

Если программа запускается с этой функцией, чтобы онаДля продолжения работы в фоновом режиме выходные данные программы должны быть перенаправлены в файл или другой поток вывода.В противном случае PHP будет зависать до тех пор, пока не закончится выполнение программы.

Поэтому, если вы перенаправите вывод в файл журнала (что в любом случае является хорошей идеей), ваш вызывающий скрипт не будетповесить и ваш почтовый скрипт будет работать в bg.

5 голосов
/ 01 июня 2017

Из всех ответов ни один не считался смехотворно простой fastcgi_finish_request , которая при вызове сбрасывает все оставшиеся выходные данные в браузер и закрывает сеанс Fastcgi и HTTP-соединение, позволяя сценарию работать вфон.

Пример:

<?php
header('Content-Type: application/json');
echo json_encode(['ok' => true]);
fastcgi_finish_request(); // The user is now disconnected from the script

// do stuff with received data,
5 голосов
/ 07 января 2011

А почему бы не сделать HTTP-запрос на сценарий и игнорировать ответ?

http://php.net/manual/en/function.httprequest-send.php

Если вы сделаете запрос по сценарию, вам нужно позвонить своему веб-серверу, который запустит его в фоновом режиме, и вы можете (в своем основном сценарии) показать сообщение, сообщающее пользователю, что сценарий запущен.

4 голосов
/ 07 февраля 2013

Самый простой способ запустить PHP-скрипт в фоновом режиме -

php script.php >/dev/null &

Сценарий будет работать в фоновом режиме, и страница также быстрее достигнет страницы действий.

4 голосов
/ 07 января 2011

Поскольку я знаю, что вы не можете сделать это простым способом (см. Fork exec и т. Д. (Не работают под окнами)), возможно, вы можете изменить подход, используя фон браузера, отправляющий форму в ajax, так что еслипочта все еще работает, у вас нет времени ожидания.Это может помочь, даже если вам придется долго заниматься.При отправке почты всегда рекомендуется использовать спулер, это может быть локальный и быстрый smtp-сервер, который принимает ваши запросы и помещает их в реальный MTA или помещает все в БД, а не использует cron, который спулирует очередь.Cron может находиться на другой машине, вызывая спулер как внешний URL:

* * * * * wget -O /dev/null http://www.example.com/spooler.php
4 голосов
/ 07 января 2011

Как насчет этого?

  1. Ваш PHP-скрипт, содержащий форму, сохраняет флаг или некоторое значение в базе данных или файле.
  2. Второй PHP-скрипт периодически опрашивает это значениеи если он установлен, он запускает сценарий электронной почты синхронно.

Этот второй сценарий PHP должен быть настроен на запуск в качестве cron.

3 голосов
/ 07 января 2011

Если вы можете получить доступ к серверу через ssh и можете запускать свои собственные сценарии, вы можете создать простой сервер fifo, используя php (хотя вам придется перекомпилировать php с поддержкой posix для fork).

Сервер действительно может быть написан на чем угодно, вы, вероятно, легко можете сделать это на python.

Или самое простое решение - отправить HttpRequest и не читать возвращаемые данные, но сервер может уничтожить сценарий до завершения его обработки.

Пример сервера:

<?php
define('FIFO_PATH', '/home/user/input.queue');
define('FORK_COUNT', 10);

if(file_exists(FIFO_PATH)) {
    die(FIFO_PATH . ' exists, please delete it and try again.' . "\n");
}

if(!file_exists(FIFO_PATH) && !posix_mkfifo(FIFO_PATH, 0666)){
    die('Couldn\'t create the listening fifo.' . "\n");
}

$pids = array();
$fp = fopen(FIFO_PATH, 'r+');
for($i = 0; $i < FORK_COUNT; ++$i) {
    $pids[$i] = pcntl_fork();
    if(!$pids[$i]) {
        echo "process(" . posix_getpid() . ", id=$i)\n";
        while(true) {
            $line = chop(fgets($fp));
            if($line == 'quit' || $line === false) break;
            echo "processing (" . posix_getpid() . ", id=$i) :: $line\n";
        //  $data = json_decode($line);
        //  processData($data);
        }
        exit();
    }
}
fclose($fp);
foreach($pids as $pid){
    pcntl_waitpid($pid, $status);
}
unlink(FIFO_PATH);
?>

Пример клиента:

<?php
define('FIFO_PATH', '/home/user/input.queue');
if(!file_exists(FIFO_PATH)) {
    die(FIFO_PATH . ' doesn\'t exist, please make sure the fifo server is running.' . "\n");
}

function postToQueue($data) {
    $fp = fopen(FIFO_PATH, 'w+');
    stream_set_blocking($fp, false); //don't block
    $data = json_encode($data) . "\n";
    if(fwrite($fp, $data) != strlen($data)) {
        echo "Couldn't the server might be dead or there's a bug somewhere\n";
    }
    fclose($fp);
}
$i = 1000;
while(--$i) {
    postToQueue(array('xx'=>21, 'yy' => array(1,2,3)));
}
?>
3 голосов
/ 07 января 2011

Фоновая работа cron звучит как хорошая идея для этого.

Вам понадобится ssh-доступ к машине, чтобы запустить скрипт как cron.

$ php scriptname.php для его запуска.

...