跳到主要內容

使用Xoops的preload機制做fsockopen非同步幕後執行不占用前端資源

其實會想用fsockopen做非同步幕後執行,主要是因為客戶要開發自動批次下載Google雲端硬碟及自動建立檔案功能,如果用以往的活人撞針方法,觸發執行的那個ip會因為自動執行畫面整個延遲卡死(UX死當),必須改用非同步方式來解決(瀏覽跟執行層分開),經過測試fsockopen確實能開一個新的HTTP請求至目標腳本然後斷線不理(非同步),腳本自動以Web Server中獨立request執行程序,完全不影響前端頁面瀏覽不延遲,能優化UX體驗,不需要用活人獻祭了(改幕後執行),以下是框架及心得分享

要建立fsockopen前需要先使用Xoops的preload機制,preload是XOOPS提供的全域事件機制,可在每次request的特定生命週期節點(如 footer)插入自訂邏輯,使所有頁面都能觸發指定行為,換句話說在模組中執行preload的腳本,Xoops全體頁面都能被觸發事件(全域事件機制),所以拿來當作fsockopen的執行footer非常適合。

preload = XOOPS 全域事件鉤子(Hook)

XOOPS 啟動流程中
→ 每個 request 都會觸發 preload
→ 依事件執行(你用的是 Footer)

建立preload方法

先在xoops模組根目錄建立一個preloads,在置入一個core.php檔案
例如:my_modules/preloads/core.php
然後加入以下代碼

<?php

class NeilalbumCorePreload extends XoopsPreloadItem {
public static function eventCoreFooterStart($args) {
//eventCoreFooterStart 所有頁面 / 在輸出footer前 /都會觸發
async_trigger();
}
}

function async_trigger() {
global $xoopsModuleConfig,$xoopsModule,$isAdmin;

//做一個$token
$secret_id="rEDBzCyHEJ7Yv32ie5JPyB0WmKP9caeDPbkpfc1L"; //可改成自己要的雜奏碼
$ts = time();
$token = hash_hmac('sha256', 'my_modules_script'.$ts, $secret_id);
$url = XOOPS_URL . '/modules/my_modules/script.php?ts='.$ts.'&token='.$token; //script.php是要非同步的執行腳本檔案
$parts = parse_url($url);
$host = $parts['host'];
$path = $parts['path'] . (isset($parts['query']) ? '?' . $parts['query'] : '');
$port = ($parts['scheme'] == 'https') ? 443 : 80;
//透過 fsockopen 主動發起一個 HTTP 請求至目標腳本,並在送出後立即關閉連線,使該腳本由 Web Server 以獨立 request 執行,達到非同步效果。
$fp = @fsockopen(
($parts['scheme'] == 'https' ? 'ssl://' : '') . $host,
$port,
$errno,
$errstr,
1
);
if ($fp) {
//以下是非同步成立的關鍵
stream_set_blocking($fp, false);
$out = "GET $path HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n"; //不等回應送出後直接斷線(腳本於Web Server獨立執行)
fwrite($fp, $out);
fclose($fp);
}
}

運作原理

PHP A(主頁)
→ fsockopen 打 HTTP 請求
→ 不等回應
→ 直接結束

PHP B(script.php)
→ 由 Web Server 接手
→ 獨立執行

到這裡完成請求端的部分,每當xoops執行preload就會發一個request執行程序到script.php且立即斷線,再來就是執行端部分,在模組根目錄建一個腳本檔,例如:my_modules/script.php

然後加入以下代碼


<?php

include "header.php";
ignore_user_abort(true); //使用者斷線繼續跑
set_time_limit(0); //避免PHP執行超時
global $xoopsModuleConfig,$xoopsModule,$isAdmin;
//log輸出-重要因為在腳本中無法ceho也不會有錯誤輸出,透過log可以查看事件進行回應
function cron_log($msg) {
file_put_contents(
XOOPS_ROOT_PATH . '/uploads/my_modules_script.log',
date('H:i:s') . ' ' . $msg . "\n",
FILE_APPEND
);
}

//LOG使用方法如下
/*
cron_log("進入script");
cron_log("ts=".$ts ?? 'NULL');
cron_log("token=".$token ?? 'NULL');
cron_log("secret_id=".$secret_id ?? 'NULL');
*/

//腳本位置 XOOPS_ROOT_PATH . '/uploads/my_modules_script.log',

//token 驗證(重要)
$secret_id="rEDBzCyHEJ7Yv32ie5JPyB0WmKP9caeDPbkpfc1L"; //跟來源端一樣
$ts = isset($_GET['ts']) ? (int)$_GET['ts'] : 0;
$token = isset($_GET['token']) ? $_GET['token'] : '';
if (empty($ts) || empty($token)) exit;
//時間限制(5分鐘)
if (abs(time() - (int)$ts) > 300) exit;
// 驗證
//$check = hash_hmac('sha256', $ts, $secret_id);
$check = hash_hmac('sha256', 'my_modules_script'.$ts, $secret_id);
if (!hash_equals($check, $token)) exit;

//這裡要放執行的腳本代碼(批次下載寄信或是自動化排程功能都可)

exit;

這樣就完成幕後執行機制建立,觸發者完全沒任何延遲,全部都由腳本在Web Server以獨立request執行,但有一點還是要注意,既使是幕後執行,仍會吃主機的I/O 資源,所以腳本最好是作分流批次處理或是上執行鎖,避免Web Server負載過重當掉。

以上工作心得分享,有需要的朋友參考看看!!

工作心得撰寫:徐嘉裕 Neil hsu

留言

這個網誌中的熱門文章

好用的windows備份檔案dos指令XCOPY-教學撰寫:徐嘉裕Neil hsu

要快速的備份某個硬碟全部資料到備份硬碟中,雖然是可以用windows內建的備份與還原,但如果重灌windows可會有key不同而無法還原的問題,還要改一堆設定煩死了,直接用dos指令XCOPY來做檔案的複製就很快了,資料也不會丟失,好朋友可以參考看看!! 首先開啟windows所有程式->附屬應用程式->開啟命令提示字元! 然後輸入以下指令 XCOPY C:\xxx   F:\xxx /s 藍字的 C:\xxx  為複製檔案來源位置例如要複製整個C槽就輸入 C:\ 綠色的   F:\xxx 為複製目的位置,例如要放到F槽的備份資料夾,就輸入  F:\ 備份 紅字的 S 為複製類型參數,可以自行修改為以下的參數設定: ================================================================= /A    只複製設定成保存屬性的檔案,不要改變屬性的設定。 /M    只複製設定成保存屬性的檔案,並清除保存屬性。 /D:m-d-y  複製指定日期當天或之後變更的檔案。如果沒給日期,只複製那些來源檔案日期比目的檔案日期為新的檔案。 /EXCLUDE:file1[+file2][+file3]...         指定檔案清單字串。每個字串應在不同行。如果有字串對應到要進行複製的檔案絕         對路徑的任何部分,這個檔案會被排除複製。例如,指定字串         \obj\ 或 .obj 的話,會排除所有在 obj 目錄下副檔名是.obj 的檔案複製。 /P    在建立每個目的檔案時顯示提示。 /S    複製每個目錄及其包含的子目錄,不複製空目錄。 /E    複製每個目錄及其包含的子目錄,也複製空目錄。/S 與 /E相同,能夠用來修改 /T。 /V   ...

XAMPP自行下載PHP官方PHP8.3升級方法

目前這方法只適用於Windows安裝的Xampp,可以下PHP官網的PHP8.3包進行升級,如果是LINUX安裝的LAMPP此方法不適用,可直接跳過! 因為本地端的開發環境需要更高的PHP版本來測試模組,而XAMPP官網釋出的PHP版本只有到PHP8.2,無法滿足需求,只好自己動手DIY升級了,其實方法也很簡單,說明如下: 1、先關閉Xampp Panel的Apache跟Mysql 2、前往PHP官網,下載PHP8.3包(VS16 x64 Thread Safe) 下載連結: https://windows.php.net/download#php-8.3-ts-vs16-x64 3、吧下載回來的php包放解壓縮放到php資料夾裡面(建立一個php資料夾),然後吧Xampp根目錄中的php檔案更名,例如改為php_8.1,再吧剛剛下載建立的php資料夾放到Xampp根目錄中c:\xampp\php 4、進入php根目錄,找到php.ini-development,複製一份後改為php.ini,用文字編輯器打開進入 5、先搜尋;extension_dir,在下面加上extension_dir = "\xampp\php\ext" 6、再搜尋;extension=curl,會有下面這一排啟用套件設定 將以下套件的;註解拿掉 extension=curl extension=fileinfo extension=gd extension=gettext extension=mbstring extension=exif      extension=mysqli extension=openssl extension=pdo_mysql extension=pdo_sqlite extension=zip 7、最後需要吧 C:\xampp\php裡面的 libssh2.dll 複製後貼到C:\xampp\apache\bin 覆蓋舊檔 再到資料夾C:\xampp\php中輸入搜尋 libcrypto-*.dll 和 libssl-*.dll 找到兩支檔,複製後貼到C:\xampp\apache\bin 覆蓋舊檔 找到php.ini中的curl.cainfo,吧註解拿掉改為 curl.cainfo = "C:\xampp\php\ext...

jQuery取得下拉選單selected中數值與內容的方法

假設選單狀態為: <select id='selectname '> <option value='v1' data-id="d1">選單A</option> <option value='v2' data-id="d2">選單B</option> <option value='v3' data-id="d3">選單C</option> </select> 以jQuery取得選單數值與內容方法如下: 1、取得下拉選單 value 數值的方法 $selectname=$('#selectname').val(); alert($selectname); //顯示選單中 selected 狀態的value數值v1 or  v2  or  v3。 2、取得下拉選單中 data-id 數值的方法 $data-id= $(this).find(':selected').attr('data-id'); alert($data-id); //顯示選單中 selected 狀態的data-id數值d1 or  d2 or  d3。 3、取得下拉選單中 文字 的方法 $selecttext=$(this).find(':selected').text(); alert($selecttext); //顯示選單中 selected 狀態的文字內容,如選單A or  選單B or 選單C。 教學撰寫:徐嘉裕 Neil hsu