Próbuję wykonać wywołanie AJAX (przez JQuery), które zainicjuje dość długi proces. Chciałbym, aby skrypt po prostu wysłał odpowiedź wskazującą, że proces się rozpoczął, ale JQuery nie zwróci odpowiedzi, dopóki skrypt PHP nie zostanie uruchomiony.
Próbowałem tego z „zamkniętym” nagłówkiem (poniżej), a także z buforowaniem wyjścia; żaden nie wydaje się działać. Jakieś przypuszczenia? czy jest to coś, co muszę zrobić w JQuery?
<?php
echo( "We'll email you as soon as this is done." );
header( "Connection: Close" );
// do some stuff that will take a while
mail( '[email protected]', "okay I'm done", 'Yup, all done.' );
?>
Odpowiedzi:
Poniższa strona podręcznika PHP (w tym notatki użytkownika) sugeruje wiele instrukcji dotyczących zamykania połączenia TCP z przeglądarką bez kończenia skryptu PHP:
Podobno wymaga to nieco więcej niż wysłania zamkniętego nagłówka.
Następnie OP potwierdza: tak , to załatwiło sprawę : wskazując na notatkę użytkownika nr 71172 (listopad 2006) skopiowaną tutaj:
Później, w lipcu 2010 r., W powiązanej odpowiedzi Arctic Fire połączył dwie dalsze uwagi użytkowników, które były następstwem powyższej:
źródło
Connection: close
nagłówek może zostać nadpisany przez inne oprogramowanie w stosie, na przykład odwrotne proxy w przypadku CGI (zauważyłem to zachowanie z nginx). Zobacz odpowiedź na ten temat od @hanshenrik. GeneralnieConnection: close
jest wykonywany po stronie klienta i nie powinien być traktowany jako odpowiedź na to pytanie. Połączenie powinno zostać zamknięte po stronie serwera .Konieczne jest wysłanie tych 2 nagłówków:
Ponieważ potrzebujesz znać rozmiar pliku wyjściowego, musisz buforować dane wyjściowe, a następnie przesłać je do przeglądarki:
Ponadto, jeśli twój serwer sieciowy używa automatycznej kompresji gzip na wyjściu (np. Apache z mod_deflate), to nie zadziała, ponieważ rzeczywisty rozmiar wyjścia jest zmieniony, a Content-Length nie jest już dokładne. Wyłącz kompresję gzip dla konkretnego skryptu.
Aby uzyskać więcej informacji, odwiedź http://www.zulius.com/how-to/close-browser-connection-continue-execution
źródło
header("Content-Encoding: none\r\n");
W ten sposób apache nie będzie ich kompresował.ob_flush()
jest to konieczne i faktycznie powoduje powiadomieniefailed to flush buffer
. Wyciągnąłem go i to działało świetnie.ob_flush()
linia jest konieczna.Możesz użyć Fast-CGI z PHP-FPM, aby użyć tej
fastcgi_end_request()
funkcji . W ten sposób możesz kontynuować przetwarzanie, gdy odpowiedź została już wysłana do klienta.Znajdziesz to w podręczniku PHP tutaj: FastCGI Process Manager (FPM) ; Jednak ta funkcja nie jest szczegółowo opisana w instrukcji. Tutaj fragment z PHP-FPM: PHP FastCGI Process Manager Wiki :
fastcgi_finish_request ()
Zakres: funkcja php
Kategoria: Optymalizacja
Ta funkcja pozwala przyspieszyć implementację niektórych zapytań php. Przyspieszenie jest możliwe, gdy w trakcie wykonywania skryptu występują akcje, które nie wpływają na odpowiedź serwera. Na przykład zapisanie sesji w memcached może nastąpić po utworzeniu strony i przekazaniu jej na serwer WWW.
fastcgi_finish_request()
to funkcja php, która zatrzymuje wyjście odpowiedzi. Serwer WWW natychmiast zaczyna przesyłać odpowiedź "powoli i niestety" do klienta, a php w tym samym czasie może zrobić wiele przydatnych rzeczy w kontekście zapytania, takich jak zapis sesji, konwersja pobranego wideo, obsługa wszelkiego rodzaju statystyk itp.fastcgi_finish_request()
może wywołać funkcję zamykającą.Uwaga:
fastcgi_finish_request()
jest to dziwactwo , gdzie do rozmowyflush
,print
alboecho
zakończy skrypt wcześnie.Aby uniknąć tego problemu, możesz zadzwonić
ignore_user_abort(true)
bezpośrednio przed lub pofastcgi_finish_request
połączeniu:źródło
Pełna wersja:
źródło
Lepszym rozwiązaniem jest rozwidlenie procesu w tle. Jest to dość proste na unix / linux:
Powinieneś spojrzeć na to pytanie, aby uzyskać lepsze przykłady:
PHP wykonuje proces w tle
źródło
Zakładając, że masz serwer Linux i dostęp do roota, spróbuj tego. To najprostsze rozwiązanie, jakie znalazłem.
Utwórz nowy katalog dla następujących plików i nadaj mu pełne uprawnienia. (Później możemy uczynić to bezpieczniejszym).
Umieść to w pliku o nazwie
bgping
.Zwróć uwagę na
&
. Polecenie ping będzie działać w tle, podczas gdy bieżący proces przejdzie do polecenia echo. Wysyła ping do www.google.com 15 razy, co zajmie około 15 sekund.Spraw, aby był wykonywalny.
Umieść to w pliku o nazwie
bgtest.php
.Kiedy żądasz bgtest.php w przeglądarce, powinieneś szybko otrzymać następującą odpowiedź, bez czekania około 15 sekund na wykonanie polecenia ping.
Na serwerze powinno być teraz uruchomione polecenie ping. Zamiast polecenia ping możesz uruchomić skrypt PHP:
Mam nadzieję że to pomoże!
źródło
Oto modyfikacja kodu Timbo, która działa z kompresją gzip.
źródło
Jestem na udostępnionym hoście i jestem
fastcgi_finish_request
skonfigurowany do całkowitego zamykania skryptów.connection: close
Rozwiązanie też mi się nie podoba . Użycie go wymusza oddzielne połączenie dla kolejnych żądań, co kosztuje dodatkowe zasoby serwera. CzytamTransfer-Encoding: cunked
artykuł Wikipedii i dowiedziałem się, że0\r\n\r\n
kończy odpowiedź. Nie przetestowałem tego dokładnie we wszystkich wersjach przeglądarek i na różnych urządzeniach, ale działa to na wszystkich 4 moich obecnych przeglądarkach.źródło
Możesz spróbować wielowątkowości.
możesz stworzyć skrypt, który wykonuje wywołanie systemowe (używając shell_exec ), które wywołuje plik binarny php ze skryptem, aby wykonać swoją pracę jako parametr. Ale nie sądzę, że jest to najbezpieczniejszy sposób. Może uda ci się to poprawić, chrootując proces php i inne rzeczy
Alternatywnie, w phpclasses jest klasa, która to robi http://www.phpclasses.org/browse/package/3953.html . Ale nie znam szczegółów implementacji
źródło
&
znaku, aby uruchomić proces w tle.Odpowiedź TL; DR:
Odpowiedź funkcji:
źródło
Twój problem można rozwiązać, wykonując równoległe programowanie w php. Pytanie o to zadałem kilka tygodni temu tutaj: Jak używać wielowątkowości w aplikacjach PHP
I otrzymałem świetne odpowiedzi. Szczególnie jeden mi się podobał. Pisarz odniósł się do samouczka Easy Parallel Processing in PHP (wrzesień 2008; autor: johnlim), który może bardzo dobrze rozwiązać twój problem, ponieważ użyłem go już do rozwiązania podobnego problemu, który pojawił się kilka dni temu.
źródło
Odpowiedź Joeri Sebrechts jest bliska, ale niszczy wszelkie istniejące treści, które mogą być buforowane, zanim zechcesz się rozłączyć. Nie wywołuje się
ignore_user_abort
poprawnie, co pozwala na przedwczesne zakończenie działania skryptu. odpowiedź dizjizmu jest dobra, ale nie ma zastosowania. Np. Osoba może mieć większe lub mniejsze bufory wyjściowe, których ta odpowiedź nie obsługuje, więc może po prostu nie działać w twojej sytuacji i nie będziesz wiedział dlaczego.Ta funkcja umożliwia odłączenie w dowolnym momencie (o ile nagłówki nie zostały jeszcze wysłane) i zachowuje treści, które do tej pory wygenerowałeś. Dodatkowy czas przetwarzania jest domyślnie nieograniczony.
Jeśli potrzebujesz dodatkowej pamięci, przydziel ją przed wywołaniem tej funkcji.
źródło
Uwaga dla użytkowników mod_fcgid (używaj na własne ryzyko).
Szybkie rozwiązanie
Przyjęta odpowiedź Joeri Sebrechts jest rzeczywiście funkcjonalna. Jeśli jednak używasz mod_fcgid , może się okazać, że to rozwiązanie nie działa samodzielnie. Innymi słowy, gdy wywoływana jest funkcja flush, połączenie z klientem nie zostaje zamknięte.
FcgidOutputBufferSize
Parametr konfiguracji mod_fcgid może być winien. Znalazłem tę wskazówkę w:Po przeczytaniu powyższego możesz dojść do wniosku, że szybkim rozwiązaniem byłoby dodanie linii (patrz „Przykład wirtualnego hosta” na końcu):
w pliku konfiguracyjnym Apache (np. httpd.conf), pliku konfiguracyjnym FCGI (np. fcgid.conf) lub w pliku hostów wirtualnych (np. httpd-vhosts.conf).
Szczegóły i drugie rozwiązanie
Powyższe rozwiązanie wyłącza buforowanie wykonywane przez mod_fcgid albo dla całego serwera, albo dla konkretnego hosta wirtualnego. Może to spowodować spadek wydajności Twojej witryny internetowej. Z drugiej strony może tak nie być, ponieważ PHP samodzielnie wykonuje buforowanie.
Jeśli nie chcesz wyłączać buforowania mod_fcgid , istnieje inne rozwiązanie ... możesz zmusić ten bufor do opróżnienia .
Poniższy kod właśnie to robi, bazując na rozwiązaniu zaproponowanym przez Joeri Sebrechts:
To, co zasadniczo robi dodana linia kodu, to wypełnienie bufora mod_fcgi , zmuszając go w ten sposób do opróżnienia . Wybrano numer „65537”, ponieważ domyślną wartością
FcgidOutputBufferSize
zmiennej jest „65536”, jak wspomniano na stronie internetowej Apache dla odpowiedniej dyrektywy . W związku z tym może być konieczne odpowiednie dostosowanie tej wartości, jeśli w środowisku ustawiono inną wartość.Moje środowisko
Przykładowy host wirtualny
źródło
to działało dla mnie
źródło
Ok, więc zasadniczo tak, jak jQuery wykonuje żądanie XHR, nawet metoda ob_flush nie będzie działać, ponieważ nie możesz uruchomić funkcji na każdej zmianie onreadystatechange. jQuery sprawdza stan, a następnie wybiera odpowiednie działania do podjęcia (ukończenie, błąd, sukces, przekroczenie limitu czasu). I chociaż nie mogłem znaleźć odniesienia, pamiętam, że nie działa to ze wszystkimi implementacjami XHR. Metoda, która moim zdaniem powinna zadziałać, to skrzyżowanie odpytywania ob_flush i forever-frame.
A ponieważ skrypty są wykonywane w tekście, gdy bufory są opróżniane, otrzymasz wykonanie. Aby było to przydatne, zmień plik console.log na metodę wywołania zwrotnego zdefiniowaną w głównym ustawieniach skryptu, aby otrzymywać dane i wykonywać na nich działania. Mam nadzieję że to pomoże. Pozdrawiam, Morgan.
źródło
Alternatywnym rozwiązaniem jest dodanie zadania do kolejki i utworzenie skryptu cron, który sprawdza dostępność nowych zadań i uruchamia je.
Ostatnio musiałem to zrobić, aby ominąć ograniczenia narzucone przez współdzielonego hosta - exec () et al został wyłączony dla PHP uruchamianego przez serwer WWW, ale mógł działać w skrypcie powłoki.
źródło
Jeśli
flush()
funkcja nie działa. Musisz ustawić następne opcje w php.ini, takie jak:źródło
Najnowsze rozwiązanie robocze
źródło
Po wypróbowaniu wielu różnych rozwiązań z tego wątku (po tym, jak żadne z nich nie działało dla mnie), znalazłem rozwiązanie na oficjalnej stronie PHP.net:
źródło