Jak uruchomić skrypt PHP w tle po przesłaniu formularza?


104

Problem
Mam formularz, który po przesłaniu uruchomi podstawowy kod w celu przetworzenia przesłanych informacji i wstawienia go do bazy danych w celu wyświetlenia na stronie internetowej z powiadomieniami. Ponadto mam listę osób, które zapisały się, aby otrzymywać te powiadomienia pocztą e-mail i SMS-em. Ta lista jest jak na razie banalna (tylko przesuwa około 150), jednak wystarczy, aby przejście przez całą tabelę subskrybentów i wysłanie ponad 150 e-maili trwało ponad minutę. (E-maile są wysyłane indywidualnie, zgodnie z żądaniem administratorów naszego serwera poczty e-mail z powodu zasad masowej poczty e-mail).

W tym czasie osoba, która opublikowała alert, będzie siedzieć na ostatniej stronie formularza przez prawie minutę bez żadnego pozytywnego potwierdzenia, że ​​jej powiadomienie zostało wysłane. Prowadzi to do innych potencjalnych problemów, z których wszystkie mają możliwe rozwiązania, które moim zdaniem są mniej niż idealne.

  1. Po pierwsze, plakat może pomyśleć, że serwer ma opóźnienia i ponownie kliknąć przycisk „Prześlij”, powodując, że skrypt uruchamia się od nowa lub uruchamia się dwukrotnie. Mogę rozwiązać ten problem za pomocą JavaScript, aby wyłączyć przycisk i zamienić tekst na coś w rodzaju „Przetwarzanie ...”, jednak jest to mniej niż idealne, ponieważ użytkownik nadal będzie utknął na stronie przez czas wykonywania skryptu. (Ponadto, jeśli JavaScript jest wyłączony, ten problem nadal istnieje).

  2. Po drugie, plakat może przedwcześnie zamknąć kartę lub przeglądarkę po przesłaniu formularza. Skrypt będzie działał na serwerze, dopóki nie podejmie próby ponownego zapisu w przeglądarce, jednak jeśli użytkownik następnie przejdzie do dowolnej strony w naszej domenie (podczas gdy skrypt jest nadal uruchomiony), przeglądarka zawiesza się, ładując stronę do czasu zakończenia działania skryptu . (Dzieje się tak tylko wtedy, gdy zamknięta jest karta lub okno przeglądarki, a nie cała aplikacja przeglądarki). Jednak jest to mniej niż idealne.

(Możliwe) Rozwiązanie
Postanowiłem, że chcę wydzielić część „e-mail” skryptu do osobnego pliku, do którego mogę zadzwonić po wysłaniu powiadomienia. Początkowo myślałem o umieszczeniu tego na stronie potwierdzenia po pomyślnym wysłaniu powiadomienia. Jednak użytkownik nie będzie wiedział, że ten skrypt działa i żadne anomalie nie będą dla niego widoczne; Ten skrypt nie może zawieść.

Ale co, jeśli mogę uruchomić ten skrypt jako proces w tle? Tak więc moje pytanie brzmi: jak mogę uruchomić skrypt PHP, aby był uruchamiany jako usługa w tle i działał całkowicie niezależnie od tego, co użytkownik zrobił na poziomie formularza?

EDYCJA: Nie można tego zrobić. Musi zostać uruchomiony natychmiast po przesłaniu formularza. To są powiadomienia o wysokim priorytecie. Ponadto administratorzy systemu obsługujący nasze serwery nie zezwalają cronom na uruchamianie ich częściej niż 5 minut.


Uruchom zadanie cron i powiadom użytkownika na innej stronie, że jego żądanie jest przetwarzane lub coś w tym rodzaju.
Evan Mulawski,

1
Czy chcesz go uruchamiać regularnie (codziennie lub co godzinę), czy po przesłaniu? Myślę, że możesz użyć PHP, exec()ale nigdy tego nie próbowałem.
Simon,

2
Po prostu używam ajaxi wstawiam sleep()z interwałem czasowym wewnątrz pętli (używam foreach w moim przypadku), aby uruchomić proces w tle. Działa doskonale na moim serwerze. Nie jestem pewien co do pozostałych. Dodam również ignore_user_abort()i set_time_limit()(z obliczonym czasem zakończenia) przed pętlą, aby upewnić się, że skrypt nie zostanie zatrzymany przez serwer, z którego korzystam, nawet według mojego testu, skrypt wykonujący zadanie bez ignore_user_abort()i set_time_limit().
Ari

Widziałem, że już wymyśliłeś rozwiązanie. Używam gearmanPHP do obsługi zadań w tle. Idealnie nadaje się do skalowania, jeśli natrafisz na duże przetwarzanie i duże obciążenia. Powinieneś się temu przyjrzeć.
Andre Garcia

Postępuj zgodnie z tą odpowiedzią w [stackoverflow] ( stackoverflow.com/a/46617107/6417678 )
Joshy Francis

Odpowiedzi:


89

Poeksperymentowałem execi shell_execodkryłem rozwiązanie, które działało idealnie! Decyduję się użyć, shell_execaby móc rejestrować każdy proces powiadamiania, który ma miejsce (lub nie). ( shell_execzwraca jako ciąg i było to łatwiejsze niż użycie exec, przypisanie wyniku do zmiennej, a następnie otwarcie pliku do zapisu).

Używam następującego wiersza, aby wywołać skrypt e-mail:

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

Ważne jest, aby zwrócić &uwagę na znak na końcu polecenia (jak wskazał @netcoder). To polecenie UNIX uruchamia proces w tle.

Dodatkowe zmienne ujęte w pojedyncze cudzysłowy po ścieżce do skryptu są ustawiane jako $_SERVER['argv']zmienne, które mogę wywołać w moim skrypcie.

Następnie skrypt poczty e-mail wyprowadza do mojego pliku dziennika za pomocą >>i wyświetli coś takiego:

[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)

Dzięki. To uratowało mi życie.
Liam Bailey

Co to jest $post_idpo ścieżce skryptu php?
Perocat

@Perocat to zmienna, którą wysyłasz do skryptu. W tym przypadku wysyła identyfikator, który będzie parametrem wywoływanego skryptu.
Chique

Wątpię, żeby to działało idealnie, patrz stackoverflow.com/questions/2212635/ ...
symcbean

1
Istnieje oczekująca sugestia edycji, aby uciec $post_id; Wolałbym rzucać go bezpośrednio na numer: (int) $post_id. (Wzywając twoją uwagę, aby zdecydować, co jest lepsze.)
Al.G.

19

Na serwerach Linux / Unix możesz wykonać zadanie w tle za pomocą 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);

&Będąc ważnym nieco tutaj. Skrypt będzie kontynuowany, nawet jeśli oryginalny skrypt się zakończył.


Działa to, gdy nie dzwonię proc_closepo zakończeniu zadania. proc_closepowoduje zawieszenie się skryptu na około 15 do 25 sekund. Musiałbym prowadzić dziennik przy użyciu informacji o potoku, więc muszę otworzyć plik do zapisu, zamknąć go, a następnie zamknąć połączenie proc. Więc niestety to rozwiązanie u mnie nie działa.
Michael Irigoyen

3
Oczywiście, że nie dzwonisz proc_close, o to chodzi! I tak, możesz przesłać potokiem do pliku z rozszerzeniem proc_open. Zobacz zaktualizowaną odpowiedź.
netcoder

@netcoder: co jeśli nie chcę przechowywać wyniku w żadnym pliku. jakie zmiany są wymagane w stdout?
naggarwal11

9

Ze wszystkich odpowiedzi żadna z nich nie brała pod uwagę śmiesznie łatwej funkcji fastcgi_finish_request , która po wywołaniu przesyła wszystkie pozostałe dane wyjściowe do przeglądarki i zamyka sesję Fastcgi i połączenie HTTP, pozwalając skryptowi działać w tle.

Przykład:

<?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,

właśnie tego szukałem, w shell_exec muszę jakoś przenieść przesłane dane do wykonywanego skryptu, co samo w sobie jest w moim przypadku zadaniem czasochłonnym i dodatkowo komplikuje sprawę.
Aurangzeb

7

PHP to exec("php script.php")potrafi.

Z instrukcji :

Jeśli program jest uruchamiany z tą funkcją, aby mógł dalej działać w tle, wyjście programu musi zostać przekierowane do pliku lub innego strumienia wyjściowego. Niezastosowanie się do tego spowoduje zawieszenie PHP do zakończenia wykonywania programu.

Więc jeśli przekierujesz dane wyjściowe do pliku dziennika (co i tak jest dobrym pomysłem), twój skrypt wywołujący nie zawiesi się, a skrypt poczty e-mail będzie działać w bg.


1
Dzięki, ale to nie zadziałało. Skrypt nadal „zawiesza się” podczas przetwarzania drugiego pliku.
Michael Irigoyen

Spójrz na komendę php.net/manual/en/function.exec.php#80582 Wygląda na to, że jest to spowodowane włączeniem Safe_mode. W systemie unix istnieje obejście tego problemu.
Simon,

Niestety tryb bezpieczny nie jest włączony w naszej instalacji. Dziwne, że wciąż wisi.
Michael Irigoyen

6

A dlaczego nie wykonać żądania HTTP w skrypcie i zignorować odpowiedzi?

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

Jeśli wykonasz żądanie w skrypcie, musisz zadzwonić do serwera WWW, uruchomi go w tle i możesz (w swoim głównym skrypcie) wyświetlić komunikat informujący użytkownika, że ​​skrypt jest uruchomiony.


4

Co powiesz na to?

  1. Twój skrypt PHP przechowujący formularz zapisuje flagę lub jakąś wartość w bazie danych lub pliku.
  2. Drugi skrypt PHP okresowo odpytuje tę wartość i jeśli została ustawiona, uruchamia skrypt Email w sposób synchroniczny.

Ten drugi skrypt PHP powinien działać jako cron.


Dziękuję, ale zaktualizowałem oryginalne pytanie. W tym przypadku Cron nie wchodzi w grę.
Michael Irigoyen

4

Jak wiem, nie da się tego zrobić w prosty sposób (zobacz fork exec itp. (Nie działa pod windows)), być może możesz odwrócić podejście, użyć tła przeglądarki, wysyłając formularz w Ajax, więc jeśli post nadal praca, nie musisz czekać.
Może to pomóc, nawet jeśli będziesz musiał długo się zastanawiać.

Jeśli chodzi o wysyłanie poczty, zawsze sugeruje się użycie bufora, może to być lokalny i szybki serwer smtp, który akceptuje twoje żądania i buforuje je do prawdziwego MTA lub umieszcza wszystko w DB, niż użycie crona, który buforuje kolejkę.
Cron może znajdować się na innym komputerze, wywołując bufor jako zewnętrzny adres URL:

* * * * * wget -O /dev/null http://www.example.com/spooler.php

3

Zadanie cron w tle wydaje się dobrym pomysłem na to.

Będziesz potrzebował dostępu ssh do maszyny, aby uruchomić skrypt jako cron.

$ php scriptname.php aby go uruchomić.


1
nie ma potrzeby ssh. Wiele hostów nie zezwala na ssh, ale obsługuje cron.
Teson,

Dziękuję, ale zaktualizowałem oryginalne pytanie. W tym przypadku Cron nie wchodzi w grę.
Michael Irigoyen

3

Jeśli możesz uzyskać dostęp do serwera przez ssh i możesz uruchamiać własne skrypty, możesz stworzyć prosty serwer FIFO za pomocą php (chociaż będziesz musiał ponownie skompilować php z posixobsługą fork).

Serwer można naprawdę napisać w dowolnym języku, prawdopodobnie można to łatwo zrobić w Pythonie.

Lub najprostszym rozwiązaniem byłoby wysłanie HttpRequest i nie odczytanie zwracanych danych, ale serwer mógłby zniszczyć skrypt przed zakończeniem przetwarzania.

Przykładowy serwer:

<?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);
?>

Przykładowy klient:

<?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

Prostszym sposobem uruchomienia skryptu PHP w tle jest

php script.php >/dev/null &

Skrypt będzie działał w tle, a strona szybciej dotrze do strony akcji.


2

Zakładając, że pracujesz na platformie * nix, użyj croni phppliku wykonywalnego.

EDYTOWAĆ:

Jest już sporo pytań dotyczących „uruchamiania php bez crona” na SO. Tu jest jeden:

Planuj skrypty bez używania CRON

To powiedziawszy, powyższa odpowiedź exec () brzmi bardzo obiecująco :)


Dziękuję, ale zaktualizowałem oryginalne pytanie. W tym przypadku Cron nie wchodzi w grę.
Michael Irigoyen

2

Jeśli korzystasz z systemu Windows, badań proc_openlub popen...

Ale jeśli jesteśmy na tym samym serwerze „Linux”, na którym działa cpanel, to jest to właściwe podejście:

#!/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
}
?>

Nie używaj fork()lub curljeśli wątpisz, że sobie z nimi poradzisz, to tak, jak nadużywanie serwera

Na koniec, w script.phppliku, który jest wywołany powyżej, zanotuj to, upewnij się, że napisałeś:

<?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

?>

Przepraszam za późniejszą edycję, ponieważ używam telefonu komórkowego i rzeczywiście jest to o wiele trudniejsze niż pisanie skryptu exec (). Lol;)
Jestem ArbZ

2

dla pracownika pracującego w tle myślę, że powinieneś wypróbować tę technikę, pomoże ona wywołać dowolną liczbę stron, wszystkie strony będą działać jednocześnie niezależnie, bez czekania na każdą odpowiedź strony jako asynchroniczną.

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: jeśli chcesz wysłać parametry adresu URL jako pętlę, postępuj zgodnie z tą odpowiedzią: https://stackoverflow.com/a/41225209/6295712


0

W moim przypadku mam 3 parametry, jeden z nich to string (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 &");

W moim Process.php mam ten kod:

if (!isset($argv[1]) || !isset($argv[2]) || !isset($argv[3]))
{   
    die("Error.");
} 

$idCurso = $argv[1];
$idDestino = $argv[2];
$mensaje = $argv[3];

0

To działa dla mnie. tyr this

exec(“php asyn.php”.” > /dev/null 2>/dev/null &“);

co jeśli mam wartości postów? z formularza i wysyłam go przez ajax i serialize()funkcję. na przykład mam index.php z formularzem i wysyłam wartość przez ajax do index2.php Chcę wykonać wysyłanie danych w index2.php w tle, co sugerujesz? dzięki
khalid JA

0

Użyj Amphp do wykonywania zadań równolegle i asynchronicznie.

Zainstaluj bibliotekę

composer require amphp/parallel-functions

Przykład kodu

<?php

require "vendor/autoload.php";

use Amp\Promise;
use Amp\ParallelFunctions;


echo 'started</br>';

$promises[1] = ParallelFunctions\parallel(function (){
    // Send Email
})();

$promises[2] = ParallelFunctions\parallel(function (){
    // Send SMS
})();


Promise\wait(Promise\all($promises));

echo 'finished';

W swoim przypadku możesz zrobić coś takiego jak poniżej

<?php

use function Amp\ParallelFunctions\parallelMap;
use function Amp\Promise\wait;

$responses = wait(parallelMap([
    'a@example.com',
    'b@example.com',
    'c@example.com',
], function ($to) {
    return send_mail($to);
}));
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.