Как я могу запустить PHP скрипт в фоновом режиме после отправки формы?
Проблема
У меня есть форма, которая при отправке запускает базовый код для обработки представленной информации и вставляет ее в базу данных для отображения на веб-сайте уведомления. Кроме того, у меня есть список людей, которые подписались на получение этих уведомлений по электронной почте и SMS-сообщению. Этот список тривиален как момент (всего лишь около 150), однако его достаточно, чтобы заставить его заняться более чем одной минуты, чтобы перебирать всю таблицу подписчиков и отправлять более 150 писем. (Письма отправляются индивидуально по запросу системных администраторов нашего почтового сервера из-за массовых политик электронной почты.)
В течение этого времени лицо, отправившее предупреждение, будет сидеть на последней странице формы в течение почти минуты без какого-либо положительного подтверждения того, что их уведомление публикуется. Это приводит к другим потенциальным проблемам, у всех, у которых есть возможные решения, которые я чувствую, меньше, чем идеалы.
-
Во-первых, плакат может подумать, что сервер отстает, и снова нажмите кнопку "Отправить", заставив script начать или запустить дважды. Я мог бы решить это, используя JavaScript, чтобы отключить кнопку и заменить текст, чтобы сказать что-то вроде "Обработка...", однако это не так идеально, потому что пользователь все равно будет застревать на странице для длины script выполнение. (Также, если JavaScript отключен, эта проблема все еще существует.)
-
Во-вторых, плакат может закрыть вкладку или браузер преждевременно после отправки формы. script будет работать на сервере до тех пор, пока он не попытается вернуться к браузеру, однако, если пользователь затем просматривает любую страницу в нашем домене (пока script все еще работает), браузер зависает, загружая страницу до тех пор, пока script закончился. (Это происходит только тогда, когда закрывается вкладка или окно браузера, а не все приложения для браузера.) Тем не менее, это меньше, чем идеально.
(возможное) решение
Я решил, что хочу разбить часть "электронной почты" script в отдельный файл, который я могу вызывать после того, как уведомление было опубликовано. Первоначально я думал о том, чтобы поместить это на страницу подтверждения после успешного уведомления. Однако пользователь не будет знать, что этот script запущен, и любые аномалии не будут очевидны для них; Этот script не может не работать.
Но что, если я могу запустить этот script в качестве фонового процесса? Итак, мой вопрос заключается в следующем: как я могу выполнить PHP скрипт для запуска в качестве фоновой службы и выполнить полностью независимую от того, что пользователь сделал на уровне формы?
EDIT: Это невозможно сделать. Он должен запускать момент отправки формы. Это высокоприоритетные уведомления. Кроме того, системные администраторы, работающие на наших серверах, запрещают запуск кронов не более 5 минут.
Ответы
Ответ 1
Выполнение некоторых экспериментов с exec
и shell_exec
Я раскрыл решение, которое отлично работало! Я выбираю использовать shell_exec
, чтобы я мог регистрировать каждый процесс уведомления (или нет). (shell_exec
возвращается как строка, и это было проще, чем использование exec
, назначение вывода переменной и открытие файла для записи.)
Я использую следующую строку для вызова электронной почты script:
shell_exec("/path/to/php /path/to/send_notifications.php '".$post_id."' 'alert' >> /path/to/alert_log/paging.log &");
Важно отметить &
в конце команды (как указано @netcoder). Эта команда UNIX запускает процесс в фоновом режиме.
Дополнительные переменные, окруженные одинарными кавычками после пути к script, устанавливаются как переменные $_SERVER['argv']
, которые я могу вызвать в моем script.
Электронная почта script затем выводит в мой файл журнала с помощью >>
и выводит что-то вроде этого:
[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)
Ответ 2
На серверах 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);
&
является важным битом здесь. script будет продолжаться, даже если исходный script закончился.
Ответ 3
PHP exec("PHP скрипт.php")
может это сделать.
Из Manual:
Если программа запускается с этой функции, чтобы продолжить работающий в фоновом режиме, выход программы должны быть перенаправлены на файл или другой выходной поток. провал для этого PHP зависнет до тех пор, пока выполнение программы завершается.
Итак, если вы перенаправляете вывод в файл журнала (что в любом случае является хорошей идеей), ваш вызов script не будет висеть, а ваш адрес электронной почты script будет запущен в bg.
Ответ 4
И почему бы не сделать HTTP-запрос на script и игнорировать ответ?
http://php.net/manual/en/function.httprequest-send.php
Если вы сделаете свой запрос на script, вам нужно позвонить, ваш веб-сервер запустит его в фоновом режиме, и вы можете (в своем главном script) показать сообщение, сообщающее пользователю, что работает script.
Ответ 5
Фоновая работа cron звучит как хорошая идея для этого.
Вам понадобится ssh доступ к машине для запуска script как cron.
$ PHP скриптname.php
, чтобы запустить его.
Ответ 6
Как насчет этого?
- Ваш PHP script, который содержит форму, сохраняет флаг или некоторое значение в базе данных или файле.
- Второй PHP script опроса для этого значения периодически, и если он был установлен, он запускает Email script синхронно.
Этот второй PHP script должен быть запущен как cron.
Ответ 7
Как я знаю, вы не можете сделать это простым способом (см. fork exec и т.д. (не работают под окнами)), возможно, вы можете отменить подход, используйте фон браузера, разместив форму в ajax, так что если пост все еще работает, у вас нет времени ожидания.
Это может помочь, даже если вам нужно сделать долгую разработку.
О отправке почты всегда предлагается использовать диспетчер очереди сообщений, может быть локальным и быстрым smtp-сервером, который принимает ваши запросы и связывает их с реальным MTA или помещает все в БД, чем использовать cron, который запускает очередь.
Cron может находиться на другой машине, вызывающей спулер в качестве внешнего URL:
* * * * * wget -O /dev/null http://www.example.com/spooler.php
Ответ 8
Если вы можете получить доступ к серверу через ssh и можете запускать свои собственные скрипты, вы можете сделать простой сервер fifo с помощью php (хотя вам придется перекомпилировать php с поддержкой posix
для fork
).
Сервер может быть написан на самом деле, вы, вероятно, можете легко это сделать в python.
Или самое простое решение будет отправлять HttpRequest и не считывать возвращаемые данные, но сервер может уничтожить script до этого завершение обработки.
Пример сервера:
<?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 a bug somewhere\n";
}
fclose($fp);
}
$i = 1000;
while(--$i) {
postToQueue(array('xx'=>21, 'yy' => array(1,2,3)));
}
?>
Ответ 9
Более простой способ запуска PHP скрипт в фоновом режиме
php script.php > /null/dev &
php будет работать в фоновом режиме, и страница также будет быстрее доходить до страницы действий.
Ответ 10
Предполагая, что вы работаете на платформе * nix, используйте cron
и php
исполняемый файл.
EDIT:
Есть довольно много вопросов, которые требуют "запускать php без cron" на SO уже. Здесь один:
Расписание сценариев без использования CRON
Тем не менее, ответ exec() выше звучит очень многообещающе:)
Ответ 11
Если вы работаете в Windows, изучите proc_open
или popen
...
Но если мы на том же сервере "Linux" запускаем cpanel, то это правильный подход:
#!/usr/bin/php
<?php
$pid = shell_exec("nohup nice php -f
'path/to/your/script.php' /dev/null 2>&1 & echo $!");
While(exec("ps $pid"))
{ //you can also have a streamer here like fprintf,
// or fgets
}
?>
Не используйте fork()
или curl
, если вы сомневаетесь, что можете справиться с ними, это просто как злоупотребление вашим сервером
Наконец, в файле script.php
, который вызывается выше, обратите внимание на это, убедитесь, что вы написали:
<?php
ignore_user_abort(TRUE);
set_time_limit(0);
ob_start();
// <-- really optional but this is pure php
//Code to be tested on background
ob_flush(); flush();
//this two do the output process if you need some.
//then to make all the logic possible
str_repeat(" ",1500);
//.for progress bars or loading images
sleep(2); //standard limit
?>
Ответ 12
В моем случае у меня есть 3 параметра, один из них - строка (mensaje):
exec("C:\wamp\bin\php\php5.5.12\php.exe C:/test/N/trunk/api/v1/Process.php $idTest2 $idTest3 \"$mensaje\" >> c:/log.log &");
В моем Process.php у меня есть этот код:
if (!isset($argv[1]) || !isset($argv[2]) || !isset($argv[3]))
{
die("Error.");
}
$idCurso = $argv[1];
$idDestino = $argv[2];
$mensaje = $argv[3];
Ответ 13
для фонового работника, я думаю, вы должны попробовать эту технику, это поможет назвать столько страниц, сколько вам понравятся, все страницы будут запускаться сразу независимо, не ожидая, что каждый ответ на страницу будет асинхронным.
form_action_page.php
<?php
post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue");
//post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue2");
//post_async("http://localhost/projectname/otherpage.php", "Keywordname=anyValue");
//call as many as pages you like all pages will run at once //independently without waiting for each page response as asynchronous.
//your form db insertion or other code goes here do what ever you want //above code will work as background job this line will direct hit before //above lines response
?>
<?php
/*
* Executes a PHP page asynchronously so the current page does not have to wait for it to finish running.
*
*/
function post_async($url,$params)
{
$post_string = $params;
$parts=parse_url($url);
$fp = fsockopen($parts['host'],
isset($parts['port'])?$parts['port']:80,
$errno, $errstr, 30);
$out = "GET ".$parts['path']."?$post_string"." HTTP/1.1\r\n";//you can use POST instead of GET if you like
$out.= "Host: ".$parts['host']."\r\n";
$out.= "Content-Type: application/x-www-form-urlencoded\r\n";
$out.= "Content-Length: ".strlen($post_string)."\r\n";
$out.= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
fclose($fp);
}
?>
testpage.php
<?
echo $_REQUEST["Keywordname"];//case1 Output > testValue
//here do your background operations it will not halt main page
?>
PS: если вы хотите отправить параметры URL-адреса в виде цикла, выполните следующий ответ: fooobar.com/questions/49521/...
Ответ 14
Из всех ответов никто не рассматривал смехотворно легкую функцию fastcgi_finish_request, которая при вызове очищает весь оставшийся вывод в браузере и закрывает Fastcgi и HTTP-соединение, позволяя script работать в фоновом режиме.
Пример:
<?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,