Symfoware

Symfowareについての考察blog

素のPHPからfcgi(php-fpm)経由でfuelPHPのコントローラーを実行

時間のかかる処理をバックグラウンドで処理するため、
素のPHPからfcgi呼び出しを試してみました。
素のPHPからfast cgiを呼び出す(PHP Fast CGI Client)

呼び出すプログラムは、何かフレームワークに乗っかっているパターンが大半だと思います。
今回はfuelPHPのコントローラーを呼び出してみます。


fuelPHPのサンプル



簡単なコントローラーを作成しました。

・call.php


  1. <?php
  2. class Controller_Call extends Controller {
  3.     public function action_index() {
  4.         echo 'Controller_Call!'.PHP_EOL;
  5.         echo 'post1:'.Input::post('post1').PHP_EOL;
  6.         echo 'post2:'.Input::post('post2').PHP_EOL;
  7.         
  8.     }
  9. }





fcgi経由での呼び出し



ミソはFCGI_PARAMSの指定です。

・呼び出すスクリプトはfuel/public/index.php
・PATH_INFOで呼び出すコントローラー名を指定

こんな感じになりました。


  1. <?php
  2. // http://saburi380.blogspot.com/2014/11/javafast-cgi-client.html
  3. class FCGIConnection {
  4.     //
  5.     // 8. Types and Constants
  6.     //
  7.     const FCGI_VERSION_1 = 1;
  8.     const FCGI_BEGIN_REQUEST = 1;
  9.     const FCGI_ABORT_REQUEST = 2;
  10.     const FCGI_END_REQUEST = 3;
  11.     const FCGI_PARAMS = 4;
  12.     const FCGI_STDIN = 5;
  13.     const FCGI_STDOUT = 6;
  14.     const FCGI_STDERR = 7;
  15.     const FCGI_DATA = 8;
  16.     const FCGI_GET_VALUES = 9;
  17.     const FCGI_GET_VALUES_RESULT = 10;
  18.     const FCGI_UNKNOWN_TYPE = 11;
  19.     const FCGI_MAXTYPE = self::FCGI_UNKNOWN_TYPE;
  20.     const FCGI_KEEP_CONN = 1;
  21.     const FCGI_RESPONDER = 1;
  22.     const FCGI_AUTHORIZER = 2;
  23.     const FCGI_FILTER = 3;
  24.     private $_socket;
  25.     private $_stream;
  26.     public function __construct() {
  27.         
  28.         
  29.         $this->_socket = socket_create(AF_UNIX, SOCK_STREAM, 0);
  30.         $ret = socket_connect($this->_socket, '/var/run/php/php7.2-fpm.sock');
  31.         if ($ret === false) {
  32.             $this->_log('open error.');
  33.         } else {
  34.             $this->_log('open success.');
  35.         }
  36.         
  37.         /*
  38.         $this->_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
  39.         $ret = socket_connect($this->_socket, '127.0.0.1', 9000);
  40.         if ($ret === false) {
  41.             $this->_log('connect error.');
  42.         } else {
  43.             $this->_log('connect success.');
  44.         }
  45.         */
  46.     }
  47.     //
  48.     // 5. Application Record Types
  49.     //
  50.     //
  51.     // 5.1 FCGI_BEGIN_REQUEST
  52.     //
  53.     public function sendBeginRequest($requestID, $keepalive) {
  54.         $this->_stream = '';
  55.         $this->sendRecordHeader($requestID, self::FCGI_BEGIN_REQUEST, 8);
  56.         $role = self::FCGI_RESPONDER;
  57.         $stream = '';
  58.         $stream .= chr($role >> 8);
  59.         $stream .= chr($role);
  60.         $stream .= $keepalive ? chr(self::FCGI_KEEP_CONN) : chr(0);
  61.     
  62.         for($i = 0; $i < 5; $i++) {
  63.             $stream .= chr(0); // padding = 5
  64.         }
  65.         $this->_send($stream);
  66.     }
  67.     //
  68.     // 5.2 Name-Value Pair Stream: FCGI_PARAMS
  69.     //
  70.     public function sendParams($requestID, $params) {
  71.         $intParamsLength = 0;
  72.         foreach($params as $key => $value) {
  73.             $intParamsLength += $this->_calcParamLength($key, $value);
  74.         }
  75.         $this->_log("FCGI_PARAMS: length=" . $intParamsLength);
  76.         $pad = $this->sendRecordHeader($requestID, self::FCGI_PARAMS, $intParamsLength);
  77.         foreach($params as $key => $value) {
  78.             $this->_sendParam($requestID, $key, $value);
  79.         }
  80.         for($i = 0; $i < $pad; $i++) {
  81.             $this->_send(chr(0));
  82.         }
  83.         $this->flush();
  84.     }
  85.     //
  86.     // 3. Protocol Basics
  87.     //
  88.     //
  89.     // 3.3 Records (All data is carried in "records")
  90.     //
  91.     public function sendRecordHeader($requestID, $recordType, $contentLength) {
  92.         $intPaddingLength = $contentLength % 8;
  93.         if($intPaddingLength != 0) {
  94.             $intPaddingLength = (8 - $intPaddingLength);
  95.         }
  96.         $this->_log(" Record Header: " . $recordType . ", pad=" . $intPaddingLength);
  97.         $stream = '';
  98.         $stream .= chr(self::FCGI_VERSION_1);
  99.         $stream .= chr($recordType);
  100.         $stream .= chr($requestID >> 8);
  101.         $stream .= chr($requestID);
  102.         $stream .= chr($contentLength >> 8);
  103.         $stream .= chr($contentLength);
  104.         $stream .= chr($intPaddingLength); // paddingLength
  105.         $stream .= chr(0); // reserved
  106.         $this->_send($stream);
  107.         return $intPaddingLength;
  108.     }
  109.     //
  110.     // 3.4 Name-Value Pairs
  111.     //
  112.     private function _calcParamLength($name, $value){
  113.         if (empty($name) || empty($value)) {
  114.             return 0;
  115.         }
  116.         $nameLength = strlen($name);
  117.         $valueLength = strlen($value);
  118.         if($nameLength < 0x80){
  119.             if($valueLength < 0x80){
  120.                 // FCGI_NameValuePair11
  121.                 return $nameLength + $valueLength + 2;
  122.             }else{
  123.                 // FCGI_NameValuePair14
  124.                 return $nameLength + $valueLength + 5;
  125.             }
  126.         }else{
  127.             if($valueLength < 0x80){
  128.                 // FCGI_NameValuePair41
  129.                 return $nameLength + $valueLength + 5;
  130.             }else{
  131.                 // FCGI_NameValuePair44
  132.                 return $nameLength + $valueLength + 8;
  133.             }
  134.         }
  135.     }
  136.     public function _sendParam($requestID, $name, $value) {
  137.         if (empty($name) || empty($value)) {
  138.             return;
  139.         }
  140.     
  141.         $nameLength = strlen($name);
  142.         $valueLength = strlen($value);
  143.         $stream = '';
  144.     
  145.         if($nameLength < 0x80){
  146.             if($valueLength < 0x80){
  147.                 // FCGI_NameValuePair11
  148.                 $stream .= chr($nameLength);
  149.                 $stream .= chr($valueLength);
  150.             }else{
  151.                 // FCGI_NameValuePair14
  152.                 $stream .= chr($nameLength);
  153.                 $stream .= chr(0x80 | $valueLength >> 24);
  154.                 $stream .= chr($valueLength >> 16);
  155.                 $stream .= chr($valueLength >> 8);
  156.                 $stream .= chr($valueLength);
  157.             }
  158.         }else{
  159.             if($valueLength < 0x80){
  160.                 // FCGI_NameValuePair41
  161.                 $stream .= chr(0x80 | $nameLength >> 24);
  162.                 $stream .= chr($nameLength >> 16);
  163.                 $stream .= chr($nameLength >> 8);
  164.                 $stream .= chr($nameLength);
  165.                 $stream .= chr($valueLength);
  166.             }else{
  167.                 // FCGI_NameValuePair44
  168.                 $stream .= chr(0x80 | $nameLength >> 24);
  169.                 $stream .= chr($nameLength >> 16);
  170.                 $stream .= chr($nameLength >> 8);
  171.                 $stream .= chr($nameLength);
  172.                 $stream .= chr(0x80 | $valueLength >> 24);
  173.                 $stream .= chr($valueLength >> 16);
  174.                 $stream .= chr($valueLength >> 8);
  175.                 $stream .= chr($valueLength);
  176.             }
  177.         }
  178.         $stream .= $name;
  179.         $stream .= $value;
  180.         $this->_send($stream);
  181.     }
  182.     //
  183.     // 5.3 Byte Streams: FCGI_STDIN
  184.     //
  185.     public function sendStdin($requestID, $body) {
  186.         $this->_log("FCGI_STDIN: length=" . strlen($body));
  187.         $pad = $this->sendRecordHeader($requestID, self::FCGI_STDIN, strlen($body));
  188.         $this->_send($body);
  189.         for($i = 0; $i < $pad; $i++) {
  190.             $this->_send(chr(0));
  191.         }
  192.         $this->flush();
  193.         
  194.     }
  195.     //
  196.     // 5.3 Byte Streams: FCGI_STDOUT, FCGI_STDERR, 5.5 FCGI_END_REQUEST
  197.     //
  198.     public function recvStdoutStderrAndWaitEndRequest() {
  199.         
  200.         $version;
  201.         $recordType = -1;
  202.         $requestID = -1;
  203.         $contentLength = 0;
  204.         $paddingLength = 0;
  205.         while(true){
  206.             if($recordType < 0){
  207.                 $raw = $this->_read();
  208.                 if ($raw === '') {
  209.                     break;
  210.                 }
  211.                 $version = ord($raw);
  212.                 if($version < 0) {
  213.                     break;
  214.                 }
  215.                 if($version != self::FCGI_VERSION_1){
  216.                     $this->_log('recv record version error: ' . $version);
  217.                     break;
  218.                 }
  219.                 
  220.                 $recordType = ord($this->_read());
  221.                 $requestID = (ord($this->_read()) << 8) + ord($this->_read());
  222.                 $contentLength = (ord($this->_read()) << 8) + ord($this->_read());
  223.                 $paddingLength = ord($this->_read());
  224.                 $this->_read(); // reserved
  225.             }
  226.             switch ($recordType) {
  227.                 case self::FCGI_STDOUT:
  228.                     $this->_log('FCGI_STDOUT: requestID=' . $requestID . ', contentLength=' . $contentLength . ', paddingLength=' . $paddingLength);
  229.                     $readed = $this->_read($contentLength);
  230.                     $this->_read($paddingLength);
  231.                     echo $readed . PHP_EOL;
  232.                     $this->_log('FCGI_STDOUT: done');
  233.                     $recordType = -1;
  234.                 break;
  235.                 case self::FCGI_STDERR:
  236.                     $this->_log('FCGI_STDERR: requestID=' . $requestID . ', contentLength=' . $contentLength . ', paddingLength=' + $paddingLength);
  237.                     $readed = $this->_read($contentLength);
  238.                     $this->_read($paddingLength);
  239.                     echo $readed;
  240.                     $this->_log('FCGI_STDERR: done');
  241.                     $recordType = -1;
  242.                 break;
  243.                 case self::FCGI_END_REQUEST:
  244.                     $startStat = (ord($this->_read()) << 24) + (ord($this->_read()) << 16) + (ord($this->_read()) << 8) + ord($this->_read());
  245.                     $endStat = ord($this->_read());
  246.                     $recordType = -1;
  247.                     $this->_log('FCGI_END_REQUEST: requestID=' . $requestID . ', appStatus=' . $startStat . ', protocolStatus=' . $endStat);
  248.                     $this->_read(3);
  249.                 break;
  250.                 default:
  251.                     $this->_log('recv record type error: ' . $recordType);
  252.                 break;
  253.             }
  254.         }
  255.         //return appStatAndProtStat;
  256.     }
  257.     private function _send($msg) {
  258.         $this->_stream .= $msg;
  259.     }
  260.     public function flush() {
  261.         
  262.         
  263.         $ret = socket_write($this->_socket, $this->_stream);
  264.         if ($ret === false) {
  265.             $this->_log('flush error!');
  266.             $this->_log($this->_stream);
  267.         } else {
  268.             $this->_log('--> flush ok');
  269.         }
  270.         $this->_stream = '';
  271.     }
  272.     private function _read($lentgh = 1) {
  273.         return socket_read($this->_socket, $lentgh);
  274.     }
  275.     public function close() {
  276.         socket_close($this->_socket);
  277.     }
  278.     private function _log($msg) {
  279.         echo $msg.PHP_EOL;
  280.     }
  281. }
  282. $fcgic = new FCGIConnection();
  283. $method = "POST";
  284. $scriptFileName = '/home/baranche/php/fuelphp/public/index.php';
  285. $queryString = "get1=get_hello&get2=get_world";
  286. $requestBodyString = "post1=post_hello&post2=post_world";
  287. $requestID = 1;
  288. // B. Typical Protocol Message Flow
  289. // {FCGI_BEGIN_REQUEST, 1, {FCGI_RESPONDER, 0}}
  290. $fcgic->sendBeginRequest($requestID, false);
  291. // {FCGI_PARAMS,         1, "..."}
  292. $params = [
  293.     'QUERY_STRING' => $queryString,
  294.     'REQUEST_METHOD' => $method,
  295.     'SCRIPT_FILENAME' => $scriptFileName,
  296.     'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
  297.     'CONTENT_LENGTH' => strlen($requestBodyString),
  298.     'PATH_INFO' => 'call'
  299. ];
  300. $fcgic->sendParams($requestID, $params);
  301. if(count($params) > 0){
  302.     // {FCGI_PARAMS,         1, ""}
  303.     $params = [];
  304.     $fcgic->sendParams($requestID, $params);
  305. }
  306. // {FCGI_STDIN,         1, "..."}
  307. $fcgic->sendStdin($requestID, $requestBodyString);
  308. // {FCGI_STDIN,         1, ""}
  309. $fcgic->sendStdin($requestID, "");
  310. // {FCGI_STDOUT,     1, "Content-type: text/html\r\n\r\n\n ... "}
  311. // {FCGI_END_REQUEST, 1, {0, FCGI_REQUEST_COMPLETE}}
  312. $fcgic->recvStdoutStderrAndWaitEndRequest();
  313. $fcgic->close();




実行してみます。


$ sudo -u www-data php sock.php
open success.
Record Header: 1, pad=0
FCGI_PARAMS: length=203
Record Header: 4, pad=5
--> flush ok
FCGI_PARAMS: length=0
Record Header: 4, pad=0
--> flush ok
FCGI_STDIN: length=33
Record Header: 5, pad=7
--> flush ok
FCGI_STDIN: length=0
Record Header: 5, pad=0
--> flush ok
FCGI_STDOUT: requestID=1, contentLength=93, paddingLength=3
Content-type: text/html; charset=UTF-8

Controller_Call!
post1:post_hello
post2:post_world

FCGI_STDOUT: done
FCGI_END_REQUEST: requestID=1, appStatus=0, protocolStatus=0



ちゃんとcallコントローラーが呼び出せました。
fuelPHPに関連するライブラリのロードもうまく動いてくれているようです。


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

  1. 2018/10/01(月) 21:26:27|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

素のPHPからfast cgiを呼び出す(PHP Fast CGI Client)

こちらで、http接続を閉じた後も処理を続行する方法を調べました。
PHP クライアントに応答した後、http connectionを閉じて重い処理を継続する

これがうまく動作しない場合があり、しょうがなく

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


でバックグランド実行していたのですが、毎回PHPプロセスを起動するので負荷が高い。

せっかくphp-fpmサービスを動作させているのだから、
PHPからphp-fpm(fcgi)を呼び出して処理を継続できないか。


Fast CGI Client



FastCGIの仕様はこちら。
FastCGI Specification

さっぱりわからんなーと思っていたら、JavaでFast CGI Clientを
実装されている方がいました。
JavaのゆるいFast CGI Client

こちらを参考にPHPを実装してみます。


実装



試行錯誤した結果の実装はこちら。
php-fpmは「/var/run/php/php7.2-fpm.sock」ソケットをリッスンしている設定です。

sock.phpと同じ階層にある「call.php」を呼び出します。

・sock.php


  1. <?php
  2. // http://saburi380.blogspot.com/2014/11/javafast-cgi-client.html
  3. class FCGIConnection {
  4.     //
  5.     // 8. Types and Constants
  6.     //
  7.     const FCGI_VERSION_1 = 1;
  8.     const FCGI_BEGIN_REQUEST = 1;
  9.     const FCGI_ABORT_REQUEST = 2;
  10.     const FCGI_END_REQUEST = 3;
  11.     const FCGI_PARAMS = 4;
  12.     const FCGI_STDIN = 5;
  13.     const FCGI_STDOUT = 6;
  14.     const FCGI_STDERR = 7;
  15.     const FCGI_DATA = 8;
  16.     const FCGI_GET_VALUES = 9;
  17.     const FCGI_GET_VALUES_RESULT = 10;
  18.     const FCGI_UNKNOWN_TYPE = 11;
  19.     const FCGI_MAXTYPE = self::FCGI_UNKNOWN_TYPE;
  20.     const FCGI_KEEP_CONN = 1;
  21.     const FCGI_RESPONDER = 1;
  22.     const FCGI_AUTHORIZER = 2;
  23.     const FCGI_FILTER = 3;
  24.     private $_socket;
  25.     private $_stream;
  26.     public function __construct() {
  27.         
  28.         
  29.         $this->_socket = socket_create(AF_UNIX, SOCK_STREAM, 0);
  30.         $ret = socket_connect($this->_socket, '/var/run/php/php7.2-fpm.sock');
  31.         if ($ret === false) {
  32.             $this->_log('open error.');
  33.         } else {
  34.             $this->_log('open success.');
  35.         }
  36.         
  37.         /*
  38.         $this->_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
  39.         $ret = socket_connect($this->_socket, '127.0.0.1', 9000);
  40.         if ($ret === false) {
  41.             $this->_log('connect error.');
  42.         } else {
  43.             $this->_log('connect success.');
  44.         }
  45.         */
  46.     }
  47.     //
  48.     // 5. Application Record Types
  49.     //
  50.     //
  51.     // 5.1 FCGI_BEGIN_REQUEST
  52.     //
  53.     public function sendBeginRequest($requestID, $keepalive) {
  54.         $this->_stream = '';
  55.         $this->sendRecordHeader($requestID, self::FCGI_BEGIN_REQUEST, 8);
  56.         $role = self::FCGI_RESPONDER;
  57.         $stream = '';
  58.         $stream .= chr($role >> 8);
  59.         $stream .= chr($role);
  60.         $stream .= $keepalive ? chr(self::FCGI_KEEP_CONN) : chr(0);
  61.     
  62.         for($i = 0; $i < 5; $i++) {
  63.             $stream .= chr(0); // padding = 5
  64.         }
  65.         $this->_send($stream);
  66.     }
  67.     //
  68.     // 5.2 Name-Value Pair Stream: FCGI_PARAMS
  69.     //
  70.     public function sendParams($requestID, $params) {
  71.         $intParamsLength = 0;
  72.         foreach($params as $key => $value) {
  73.             $intParamsLength += $this->_calcParamLength($key, $value);
  74.         }
  75.         $this->_log("FCGI_PARAMS: length=" . $intParamsLength);
  76.         $pad = $this->sendRecordHeader($requestID, self::FCGI_PARAMS, $intParamsLength);
  77.         foreach($params as $key => $value) {
  78.             $this->_sendParam($requestID, $key, $value);
  79.         }
  80.         for($i = 0; $i < $pad; $i++) {
  81.             $this->_send(chr(0));
  82.         }
  83.         $this->flush();
  84.     }
  85.     //
  86.     // 3. Protocol Basics
  87.     //
  88.     //
  89.     // 3.3 Records (All data is carried in "records")
  90.     //
  91.     public function sendRecordHeader($requestID, $recordType, $contentLength) {
  92.         $intPaddingLength = $contentLength % 8;
  93.         if($intPaddingLength != 0) {
  94.             $intPaddingLength = (8 - $intPaddingLength);
  95.         }
  96.         $this->_log(" Record Header: " . $recordType . ", pad=" . $intPaddingLength);
  97.         $stream = '';
  98.         $stream .= chr(self::FCGI_VERSION_1);
  99.         $stream .= chr($recordType);
  100.         $stream .= chr($requestID >> 8);
  101.         $stream .= chr($requestID);
  102.         $stream .= chr($contentLength >> 8);
  103.         $stream .= chr($contentLength);
  104.         $stream .= chr($intPaddingLength); // paddingLength
  105.         $stream .= chr(0); // reserved
  106.         $this->_send($stream);
  107.         return $intPaddingLength;
  108.     }
  109.     //
  110.     // 3.4 Name-Value Pairs
  111.     //
  112.     private function _calcParamLength($name, $value){
  113.         if (empty($name) || empty($value)) {
  114.             return 0;
  115.         }
  116.         $nameLength = strlen($name);
  117.         $valueLength = strlen($value);
  118.         if($nameLength < 0x80){
  119.             if($valueLength < 0x80){
  120.                 // FCGI_NameValuePair11
  121.                 return $nameLength + $valueLength + 2;
  122.             }else{
  123.                 // FCGI_NameValuePair14
  124.                 return $nameLength + $valueLength + 5;
  125.             }
  126.         }else{
  127.             if($valueLength < 0x80){
  128.                 // FCGI_NameValuePair41
  129.                 return $nameLength + $valueLength + 5;
  130.             }else{
  131.                 // FCGI_NameValuePair44
  132.                 return $nameLength + $valueLength + 8;
  133.             }
  134.         }
  135.     }
  136.     public function _sendParam($requestID, $name, $value) {
  137.         if (empty($name) || empty($value)) {
  138.             return;
  139.         }
  140.     
  141.         $nameLength = strlen($name);
  142.         $valueLength = strlen($value);
  143.         $stream = '';
  144.     
  145.         if($nameLength < 0x80){
  146.             if($valueLength < 0x80){
  147.                 // FCGI_NameValuePair11
  148.                 $stream .= chr($nameLength);
  149.                 $stream .= chr($valueLength);
  150.             }else{
  151.                 // FCGI_NameValuePair14
  152.                 $stream .= chr($nameLength);
  153.                 $stream .= chr(0x80 | $valueLength >> 24);
  154.                 $stream .= chr($valueLength >> 16);
  155.                 $stream .= chr($valueLength >> 8);
  156.                 $stream .= chr($valueLength);
  157.             }
  158.         }else{
  159.             if($valueLength < 0x80){
  160.                 // FCGI_NameValuePair41
  161.                 $stream .= chr(0x80 | $nameLength >> 24);
  162.                 $stream .= chr($nameLength >> 16);
  163.                 $stream .= chr($nameLength >> 8);
  164.                 $stream .= chr($nameLength);
  165.                 $stream .= chr($valueLength);
  166.             }else{
  167.                 // FCGI_NameValuePair44
  168.                 $stream .= chr(0x80 | $nameLength >> 24);
  169.                 $stream .= chr($nameLength >> 16);
  170.                 $stream .= chr($nameLength >> 8);
  171.                 $stream .= chr($nameLength);
  172.                 $stream .= chr(0x80 | $valueLength >> 24);
  173.                 $stream .= chr($valueLength >> 16);
  174.                 $stream .= chr($valueLength >> 8);
  175.                 $stream .= chr($valueLength);
  176.             }
  177.         }
  178.         $stream .= $name;
  179.         $stream .= $value;
  180.         $this->_send($stream);
  181.     }
  182.     //
  183.     // 5.3 Byte Streams: FCGI_STDIN
  184.     //
  185.     public function sendStdin($requestID, $body) {
  186.         $this->_log("FCGI_STDIN: length=" . strlen($body));
  187.         $pad = $this->sendRecordHeader($requestID, self::FCGI_STDIN, strlen($body));
  188.         $this->_send($body);
  189.         for($i = 0; $i < $pad; $i++) {
  190.             $this->_send(chr(0));
  191.         }
  192.         $this->flush();
  193.         
  194.     }
  195.     //
  196.     // 5.3 Byte Streams: FCGI_STDOUT, FCGI_STDERR, 5.5 FCGI_END_REQUEST
  197.     //
  198.     public function recvStdoutStderrAndWaitEndRequest() {
  199.         
  200.         $version;
  201.         $recordType = -1;
  202.         $requestID = -1;
  203.         $contentLength = 0;
  204.         $paddingLength = 0;
  205.         while(true){
  206.             if($recordType < 0){
  207.                 $raw = $this->_read();
  208.                 if ($raw === '') {
  209.                     break;
  210.                 }
  211.                 $version = ord($raw);
  212.                 if($version < 0) {
  213.                     break;
  214.                 }
  215.                 if($version != self::FCGI_VERSION_1){
  216.                     $this->_log('recv record version error: ' . $version);
  217.                     break;
  218.                 }
  219.                 
  220.                 $recordType = ord($this->_read());
  221.                 $requestID = (ord($this->_read()) << 8) + ord($this->_read());
  222.                 $contentLength = (ord($this->_read()) << 8) + ord($this->_read());
  223.                 $paddingLength = ord($this->_read());
  224.                 $this->_read(); // reserved
  225.             }
  226.             switch ($recordType) {
  227.                 case self::FCGI_STDOUT:
  228.                     $this->_log('FCGI_STDOUT: requestID=' . $requestID . ', contentLength=' . $contentLength . ', paddingLength=' . $paddingLength);
  229.                     $readed = $this->_read($contentLength);
  230.                     $this->_read($paddingLength);
  231.                     echo $readed . PHP_EOL;
  232.                     $this->_log('FCGI_STDOUT: done');
  233.                     $recordType = -1;
  234.                 break;
  235.                 case self::FCGI_STDERR:
  236.                     $this->_log('FCGI_STDERR: requestID=' . $requestID . ', contentLength=' . $contentLength . ', paddingLength=' + $paddingLength);
  237.                     $readed = $this->_read($contentLength);
  238.                     $this->_read($paddingLength);
  239.                     echo $readed;
  240.                     $this->_log('FCGI_STDERR: done');
  241.                     $recordType = -1;
  242.                 break;
  243.                 case self::FCGI_END_REQUEST:
  244.                     $startStat = (ord($this->_read()) << 24) + (ord($this->_read()) << 16) + (ord($this->_read()) << 8) + ord($this->_read());
  245.                     $endStat = ord($this->_read());
  246.                     $recordType = -1;
  247.                     $this->_log('FCGI_END_REQUEST: requestID=' . $requestID . ', appStatus=' . $startStat . ', protocolStatus=' . $endStat);
  248.                     $this->_read(3);
  249.                 break;
  250.                 default:
  251.                     $this->_log('recv record type error: ' . $recordType);
  252.                 break;
  253.             }
  254.         }
  255.         //return appStatAndProtStat;
  256.     }
  257.     private function _send($msg) {
  258.         $this->_stream .= $msg;
  259.     }
  260.     public function flush() {
  261.         
  262.         
  263.         $ret = socket_write($this->_socket, $this->_stream);
  264.         if ($ret === false) {
  265.             $this->_log('flush error!');
  266.             $this->_log($this->_stream);
  267.         } else {
  268.             $this->_log('--> flush ok');
  269.         }
  270.         $this->_stream = '';
  271.     }
  272.     private function _read($lentgh = 1) {
  273.         return socket_read($this->_socket, $lentgh);
  274.     }
  275.     public function close() {
  276.         socket_close($this->_socket);
  277.     }
  278.     private function _log($msg) {
  279.         echo $msg.PHP_EOL;
  280.     }
  281. }
  282. $fcgic = new FCGIConnection();
  283. $method = "POST";
  284. $scriptFileName = realpath(__DIR__).DIRECTORY_SEPARATOR.'call.php';
  285. $queryString = "get1=get_hello&get2=get_world";
  286. $requestBodyString = "post1=post_hello&post2=post_world";
  287. $requestID = 1;
  288. // B. Typical Protocol Message Flow
  289. // {FCGI_BEGIN_REQUEST, 1, {FCGI_RESPONDER, 0}}
  290. $fcgic->sendBeginRequest($requestID, false);
  291. // {FCGI_PARAMS,         1, "..."}
  292. $params = [
  293.     'QUERY_STRING' => $queryString,
  294.     'REQUEST_METHOD' => $method,
  295.     'SCRIPT_FILENAME' => $scriptFileName,
  296.     'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
  297.     'CONTENT_LENGTH' => strlen($requestBodyString)
  298. ];
  299. $fcgic->sendParams($requestID, $params);
  300. if(count($params) > 0){
  301.     // {FCGI_PARAMS,         1, ""}
  302.     $params = [];
  303.     $fcgic->sendParams($requestID, $params);
  304. }
  305. // {FCGI_STDIN,         1, "..."}
  306. $fcgic->sendStdin($requestID, $requestBodyString);
  307. // {FCGI_STDIN,         1, ""}
  308. $fcgic->sendStdin($requestID, "");
  309. // {FCGI_STDOUT,     1, "Content-type: text/html\r\n\r\n\n ... "}
  310. // {FCGI_END_REQUEST, 1, {0, FCGI_REQUEST_COMPLETE}}
  311. $fcgic->recvStdoutStderrAndWaitEndRequest();
  312. $fcgic->close();



呼び出すプログラムはこちら。

・call.php

  1. <?php
  2. echo 'get1:'.$_GET['get1'].PHP_EOL;
  3. echo 'get2:'.$_GET['get2'].PHP_EOL;
  4. echo 'post1:'.$_POST['post1'].PHP_EOL;
  5. echo 'post2:'.$_POST['post2'].PHP_EOL;



※こちらに添付しておきます。
https://bitbucket.org/snippets/symfo/nedeK9




実行



ソケットはwww-dataユーザーで起動しているのでユーザーを変更して実行します。


$ sudo -u www-data php sock.php



うまく行きました。


open success.
Record Header: 1, pad=0
FCGI_PARAMS: length=176
Record Header: 4, pad=0
--> flush ok
FCGI_PARAMS: length=0
Record Header: 4, pad=0
--> flush ok
FCGI_STDIN: length=33
Record Header: 5, pad=7
--> flush ok
FCGI_STDIN: length=0
Record Header: 5, pad=0
--> flush ok
FCGI_STDOUT: requestID=1, contentLength=106, paddingLength=6
Content-type: text/html; charset=UTF-8

get1:get_hello
get2:get_world
post1:post_hello
post2:post_world

FCGI_STDOUT: done
FCGI_END_REQUEST: requestID=1, appStatus=0, protocolStatus=0






重い処理の実行



call.phpを変更し、10秒待たせてみます。

・call.php


  1. <?php
  2. echo 'get1:'.$_GET['get1'].PHP_EOL;
  3. echo 'get2:'.$_GET['get2'].PHP_EOL;
  4. echo 'post1:'.$_POST['post1'].PHP_EOL;
  5. echo 'post2:'.$_POST['post2'].PHP_EOL;
  6. // 10秒待つ
  7. sleep(10);
  8. file_put_contents('/tmp/debug.txt', 'fin!');




この状態でsock.phpを実行すると10秒待たされますが、結果を取得する箇所をコメントします。


// {FCGI_STDOUT,     1, "Content-type: text/html\r\n\r\n\n ... "}
// {FCGI_END_REQUEST, 1, {0, FCGI_REQUEST_COMPLETE}}
//$fcgic->recvStdoutStderrAndWaitEndRequest();



コメント後実行すると、sock.php自体はすぐに終了しますが、
10秒後/tmp/debug.txtが作成されることが確認できると思います。

これで重い処理をphp-fpmに委譲することができました。

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

  1. 2018/10/01(月) 21:12:51|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

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. | 編集
次のページ