Symfoware

Symfowareについての考察blog

PHP シグナル送信による安全なバッチプログラムの停止(pcntl_signal)

例えばこんなプログラム。

・batch.php


  1. <?php
  2. while(true) {
  3.     // ここから
  4.     file_put_contents('log.txt', 'start,', FILE_APPEND);
  5.     sleep(1);
  6.     file_put_contents('log.txt', 'contents_get,', FILE_APPEND);
  7.     sleep(1);
  8.     file_put_contents('log.txt', 'parse,', FILE_APPEND);
  9.     sleep(1);
  10.     file_put_contents('log.txt', 'end'.PHP_EOL, FILE_APPEND);
  11.     sleep(1);
  12.     // ここまでの処理が1セット
  13.     
  14. }



ファイルに「start,contents_get,parse,end」と出力するだけの処理です。
startからendまでの出力を合わせて意味のある処理だと仮定します。
※外部リソースを取得して解析、データベースに保存するなど。

コマンドでプログラムを実行


$ nohup php batch.php > nohup.out 2> nohup.err &
[1] 3142



プログラムの終了をkillコマンドで実行すると...


$ kill 3142



中途半端な箇所で処理を停止する可能性大です。

・log.txt


start,contents_get,parse,end
start,contents_get,parse,end
start,(続きがない...)



endまで処理してから緩やかにプログラムを終了してほしい。
killコマンドにシグナルのオプションつけて終了させれば良いのでは?



シグナルハンドラ



こちらが非常に参考になりました。
PHP のシグナルハンドラのいろいろ
PHPとシグナル、その裏側
非同期シグナルハンドリング

わかったことは、
・pcntl_signalで監視したいシグナルを登録する
・シグナル送信チェックの為、ticksで監視する必要がある。
・PHP 7.1以降はticksではなく「pcntl_async_signals」が使える。

上記を踏まえ、kill -SIGQUITで緩やかに終了するバッチに修正してみます。

・batch.php


  1. <?php
  2. // プログラムを終了するか判定フラグ
  3. $term = false;
  4. // 非同期シグナルを有効にします
  5. pcntl_async_signals(true);
  6. // SIGQUITを監視
  7. // 受信したら$termをtrueにし、プログラムを終了する
  8. pcntl_signal(SIGQUIT, function($sig) use (&$term) {
  9.     file_put_contents('log.txt', '[SIGQUIT!]', FILE_APPEND);
  10.     $term = true;
  11. });
  12. while(true) {
  13.     // ここから
  14.     file_put_contents('log.txt', 'start,', FILE_APPEND);
  15.     sleep(1);
  16.     file_put_contents('log.txt', 'contents_get,', FILE_APPEND);
  17.     sleep(1);
  18.     file_put_contents('log.txt', 'parse,', FILE_APPEND);
  19.     sleep(1);
  20.     file_put_contents('log.txt', 'end'.PHP_EOL, FILE_APPEND);
  21.     sleep(1);
  22.     // ここまでの処理が1セット
  23.     // 終了フラグをチェック
  24.     if ($term) {
  25.         file_put_contents('log.txt', 'terminate'.PHP_EOL, FILE_APPEND);
  26.         break;
  27.     }
  28.     
  29. }



実行


$ nohup php batch.php > nohup.out 2> nohup.err &
[1] 3218



任意のタイミングで「-SIGQUIT」オプション付きでkill実行


$ kill -SIGQUIT 3218



・log.txt


start,contents_get,parse,end
start,contents_get,parse,end
start,contents_get,parse,[SIGQUIT!]end
terminate



ちゃんとkill -SIGQUIT受信後も処理を継続し、キリの良いところで終了してくれました。




【参考URL】
PHP のシグナルハンドラのいろいろ
PHPとシグナル、その裏側
非同期シグナルハンドリング
SIGNAL
pcntl_signal

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/08/15(水) 22:51:59|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

PHP URLパラメーターに使える適当なハッシュ値を生成する

URLパラメーター1つに複数の情報を詰め込む。
暗号化までは行かなくてよいが、簡単に推測できないようなハッシュ値が作りたい。

Fuelphpでの例ですが、こんなイメージ。
1つのパラメーターにユーザーIDとリダイレクト先のURLを入れたい。


  1. <?php
  2. class Controller_Test extends Controller
  3. {
  4.     public function action_index()
  5.     {
  6.         echo Uri::create('test/redirect/[何かしらのパラメーター]');
  7.     }
  8.     public function action_redirect($parameter) {
  9.         // パラメーターを解析
  10.         $data = some_function($parameter);
  11.         // アクセスログを記録
  12.         Log::write($data['user'] . 'access!');
  13.         // リダイレクト実行
  14.         Response::redirect($data['url']);
  15.     }
  16. }




gzcompressとbase64_encode



文字列を圧縮してbase64にすればいいのでは。

gzcompress
base64_encode


  1. <?php
  2. class Controller_Test extends Controller
  3. {
  4.     public function action_index()
  5.     {
  6.         $data = json_encode(['user' => 'symfoware', 'url' => 'https://www.google.com/']);
  7.         // 圧縮してbase64
  8.         $parameter = base64_encode(gzcompress($data));
  9.         echo Uri::create('test/redirect/'.$parameter);
  10.     }
  11.     public function action_redirect($parameter) {
  12.         // パラメーターを解析
  13.         $json = gzuncompress(base64_decode($parameter));
  14.         $data = json_decode($json, true);
  15.         // アクセスログを記録
  16.         Log::error($data['user'] . ' access!');
  17.         // リダイレクト実行
  18.         Response::redirect($data['url']);
  19.     }
  20. }



こんなURLが生成されました。

915_01.png

表示されたURLに遷移すると、狙い通りアクセスログの記録とリダイレクトが実行されました。

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/08/01(水) 22:18:05|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

FuelPHP CacheドライバにRedisを使用する

FuelPHPのキャッシュ
Cache クラス

Redisドライバを使用して、Cache クラスを利用してみます。


設定



fuel/app/config/development/に「cache.php」を新規作成します。
内容は以下のとおり。

・fuel/app/config/development/cache.php


  1. <?php
  2. return array(
  3.     // default storage driver
  4.     'driver'     => 'redis',
  5.     // default expiration (null = no expiration)
  6.     'expiration' => null,
  7.     // specific configuration settings for the redis driver
  8.     'redis' => array(
  9.         'database' => 'default', // name of the redis database to use (as configured in config/db.php)
  10.     ),
  11. );



driverに「redis」を指定し、設定は「default」を利用するよう指定しました。


続いて、fuel/app/config/development/db.phpを編集します。

・fuel/app/config/development/db.php


  1. <?php
  2. /**
  3. * The development database settings. These get merged with the global settings.
  4. */
  5. return array(
  6.     'default' => array(
  7.         'connection' => array(
  8.             'dsn'        => 'mysql:host=localhost;dbname=fuel_dev',
  9.             'username' => 'root',
  10.             'password' => 'root',
  11.         ),
  12.     ),
  13.     // --- 以下を追記
  14.     'redis' => array(
  15.         'default' => array(
  16.             'hostname' => '127.0.0.1',
  17.             'port'     => 6379,
  18.             'timeout'    => null,
  19.             'database' => 0,
  20.         ),
  21.     ),
  22. );



Redisに接続するための設定を記載します。

これで設定ファイルの準備は完了です。



サンプルプログラム



fuel/app/classes/controller/test.phpを作成。
適当なサンプルを記載します。


  1. <?php
  2. class Controller_Test extends Controller
  3. {
  4.     public function action_index()
  5.     {
  6.         $counter = 0;
  7.         try {
  8.             $counter = Cache::get('counter');
  9.         } catch (\CacheNotFoundException $e) {
  10.             // 初回、キーが存在しない場合はエラーになる
  11.         }
  12.         
  13.         echo $counter;
  14.         Cache::set('counter', $counter + 1);
  15.     }
  16. }



これで、http://[サーバーIP]/testに接続するたび、
カウントが上昇するはずです。

914_01.png

914_02.png

914_03.png

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/07/29(日) 22:06:16|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

PHP クライアントに応答した後、http connectionを閉じて重い処理を継続する

webサーバーへのリクエストが行われた時、メール送信や画像変換など
時間のかかる処理を行う場合、一旦レスポンスを返してからじっくり処理したい。

exec("nohup php background.php > /dev/null &");

を実行して、別プロセスに投げるのもよいですが、これだとwebリクエストとバッチ処理で
2つのphpプロセスが起動してしまうのがもったいない気がします。


Connection: close



レスポンスを書き込んだ後、とっとと接続を閉じればいいんじゃないかと思い調べていると、
こちらのページに行き着きました。

close a connection early
How to kill an HTTP connection and continue processing (PHP and Apache2)

「Connection: close」をヘッダーに出力すれば良いようです。

サンプルはこんな感じになりました。


  1. <?php
  2. ob_start();
  3. echo 'ok';
  4. header("Connection: close");
  5. header("Content-length: " . (string)ob_get_length());
  6. ob_end_flush();
  7. ob_flush();
  8. flush();
  9. sleep(10);
  10. file_put_contents('debug.txt', 'fin');



ブラウザでアクセスすると、すぐに「ok」と表示されます。
10秒後、サーバーにdebug.txtファイルが出力されました。

プログラムからのアクセスも、すぐに応答が得られます。
こんなPythonプログラムで試してみました。


  1. # -*- coding:utf-8 -*-
  2. import urllib2
  3. url = 'http://192.168.1.102:8000/'
  4. request = urllib2.Request(url)
  5. # 送信実行
  6. response = urllib2.urlopen(request)
  7. ret = response.read()
  8. print('Response:', ret)



実行すると一瞬で応答が得られます。


$ python sample.py
('Response:', 'ok')



これは使えそう。



【参考URL】

close a connection early
How to kill an HTTP connection and continue processing (PHP and Apache2)

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/06/28(木) 21:52:49|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

FuelPHP コントローラーのafterで共通処理を記載する

例えばこんなコントローラーとビューがあるとします。

・sample.php


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_hoge() {
  4.         $data = [
  5.             'info_list' => [
  6.                 'hoge-1',
  7.                 'hoge-2'
  8.             ]
  9.         ];
  10.         return View::forge('sample/hoge', $data);
  11.     }
  12.     public function action_piyo() {
  13.         $data = [
  14.             'info_list' => [
  15.                 'piyo-1',
  16.                 'piyo-2'
  17.             ]
  18.         ];
  19.         return View::forge('sample/piyo', $data);
  20.     }
  21. }




・view/sample/hoge.php


  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <meta charset="utf-8">
  5.     <title>hoge!</title>
  6. </head>
  7. <body>
  8.     <h3>hoge</h3>
  9.     <ul>
  10.     <?php foreach($info_list as $info) { ?>
  11.         <li><?php echo($info); ?></li>
  12.     <?php } ?>
  13.     </ul>
  14. </body>
  15. </html>




・view/sample/piyo.php


  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <meta charset="utf-8">
  5.     <title>piyo!</title>
  6. </head>
  7. <body>
  8.     <h3>piyo</h3>
  9.     <ul>
  10.     <?php foreach($info_list as $info) { ?>
  11.         <li><?php echo($info); ?></li>
  12.     <?php } ?>
  13.     </ul>
  14. </body>
  15. </html>




http://[server]/sample/hoge

857_01.png


http://[server]/sample/piyo

857_02.png



両方のビューで共通の「info_list」という配列を使用しリストを表示しています。
双方、リストの末尾に同じ文字を追加したい。



after



afterを実装して実現してみます。
https://fuelphp.com/docs/general/controllers/base.html


afterの引数は、Viewオブジェクトになっています。
Viewに表示したいデータを追加。
戻り値はResponseオブジェクトでなくてはいけないので、parent::afterで変換しています。


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_hoge() {
  4.         $data = [
  5.             'info_list' => [
  6.                 'hoge-1',
  7.                 'hoge-2'
  8.             ]
  9.         ];
  10.         return View::forge('sample/hoge', $data);
  11.     }
  12.     public function action_piyo() {
  13.         $data = [
  14.             'info_list' => [
  15.                 'piyo-1',
  16.                 'piyo-2'
  17.             ]
  18.         ];
  19.         return View::forge('sample/piyo', $data);
  20.     }
  21.     public function after($response) {
  22.         // この時点ではViewオブジェクト
  23.         Log::error(get_class($response));
  24.         if ($response instanceof View) {
  25.             Log::error('Viewです');
  26.         }
  27.         if (isset($response->info_list)) {
  28.             $response->info_list[] = 'show-all';
  29.         } else {
  30.             $response->info_list = ['show-all'];
  31.         }
  32.         // これでResponseオブジェクトとなる
  33.         $response = parent::after($response);
  34.         Log::error(get_class($response));
  35.         return $response;
  36.     }
  37. }




これで狙い通りの動作になりました。

857_03.png

857_04.png

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/04/23(月) 22:08:12|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
次のページ