非同期で呼び出したPHPで重い処理をしてもタイムアウトさせない方法
画面からサーバーサイドに処理を投げるとき、時間がかかる重たい処理を行わなければならない時があります。
たとえば、大量のデータ更新や動画のアップロード、ファイル出力や他システムの連携などがあると思います。
そんな時、どうしても発生してしまうのがタイムアウトです。
ファイルのアップロードなどですでに時間を使っているのに、利用者はデータ処理にさらに時間を使われて、イライラが止まりません。
そんな時、ちょっとした方法で避けることができますので、それをご紹介します。
目次
コマンドで実行する
画面から非同期で実行されるPHPプログラム「A」から、重たいプログラムを実行するPHPプログラム「B」を コマンドライン で実行する方法です。
具体的には下記のようなコマンドになります。
1 2 3 |
exec('php /path/to/B.php > /dev/null &'); |
ポイントは > /dev/null & です。
これはコマンドラインで非同期にする手段ですが、Linuxでのみ動きます。
Windowsでは start "" をコマンドの前につけることで非同期にできます。
制限
ただし、この方法にはいくつかの制限があります。
パラメータに上限がある
コマンドラインには長さに制限があります。
Windowsには 合計長で8191文字まで という制限があります。
LinuxはOSによって異なりますが getconf ARG_MAX で取得できるサイズまでです。
つまり、プログラム「B」に渡すパラメータには限りがあるということになります。
これはパラメータを外部ファイルにすることで回避が可能ですが、多少手間がかかります。
一部のスーパーグローバル変数が利用できない
この方法ではWebサーバーを経由せずにPHPが実行されます。
そのため、実行されるプログラム「B」では $_SERVER に入ってくるリファラや Cookie などが使用できません。
通情のPHP開発で慣れている人は、多少癖がある書き方になります。
curlで実行する
画面から非同期で実行されるPHPプログラム「A」から、重たいプログラムを実行するPHPプログラム「B」を curl で実行する方法です。
1 2 3 4 5 6 7 8 |
$url = "プログラム「B」までのURL"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_TIMEOUT, 1); curl_exec($ch); curl_close($ch); |
ポイントは curl_setopt($ch, CURLOPT_TIMEOUT, 1); です。
実はこれで実行したプログラムは非同期で動きます。
ただし、Webアクセスと同じなので、呼び出される側のプログラム「B」はタイムアウトを0にしておくなどの処理が必要です。
制限
ただし、この方法にもいくつかの制限があります。
一部のスーパーグローバル変数が利用できない
この方法ではWebサーバーを経由せずにPHPが実行されます。
そのため、実行されるプログラム「B」では $_SERVER に入ってくるリファラや Cookie などが使用できません。
通情のPHP開発で慣れている人は、多少癖がある書き方になります。
セッションを共有できない
これはスーパーグローバル変数に関する内容と重複している部分があります。
ログインをセッションで管理しているなどの場合、プログラム「B」では引き継げません。
これはCookieをヘッダーに含めることで対応することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Cookie情報を取得して、CURLOPT_HTTPHEADER形式へ変換 $cookie = ''; if(is_array($_COOKIE) && !empty($_COOKIE)) { $cookieAry = []; foreach($_COOKIE as $key => $val) { $cookieAry[] = $key.'='.$val.';'; } $cookie = implode(' ', $cookieAry); } $header = [ 'Cookie: '.$cookie, ]; // CURLのオプションでヘッダーを設定 curl_setopt($ch, CURLOPT_HTTPHEADER, $header); |
処理の中断を返して続きをそのまま処理する
これは少し説明が必要ですが、まずはプログラムを見てみましょう。
1 2 3 4 5 6 |
ignore_user_abort(true); set_time_limit(0); header('Connection: close'); header('Content-Length: 0'); |
このプログラムの後で重たい処理を書けばOKです(ただし、無限ループなどに要注意)。
この時点で画面側にはコネクション終了が返され、正常終了となります。
しかし、PHPのプログラムはそのあとの部分も実行されるため、ユーザーは終了を検知しつつも、重たいプログラムをそのまま実行することができます。
ただ、この時に画面側へ出力を行いたい場合
ob_start などを駆使する必要があります。
(php-fpmの場合
fastcgi_finish_request なども必要になります。)
また、処理が確実に終了するように、最後に exit なども忘れずに記述しましょう。
制限
通常のレスポンスが返しづらい
通常は echo などで出力すればOKですが ob_start のあとでしか返せないため、作り方に制限を受けます。
また、Webサーバ次第で必要なプログラムが変わることもあり、煩雑になります。
データフローが複数になる
1のプログラムで出力結果が2つ(画面 + プログラム終了)になるため、動きが複雑になりがちです。
プログラム次第で多少改善しますが、根本的には変わらないため、仕様書などが書きづらくなる要因でもあります。
プログラムの終了が受け取りづらい
画面側では上記プログラムの時点で終了を受け取っていますが、そのあとの重い処理が完了したかは受け取れません。
register_shutdown_function などで無理やり取得することもできるみたいですが、基本的に一方通行となります。
さいごに
いかがでしたでしょうか。
通常は同期処理のプログラムによる非同期処理は、そこそこ需要があるのではないでしょうか。
実際の実務でも使用できるため、ぜひ試してみてください。
ではでは。
- おすすめ記事
-
-
のえる2021.10.29
-
CakePHPでVue.jsを利用する方法
のえる2021.01.12 -
たった5分でPHPのファイルアップロードを作る方法
のえる2019.09.11
-
POPULAR
のえる
Full-stack Developer