2022年2月15日 星期二

[PHP] 使用 Built-in web server 和 Router Script 開發嵌入式產品的網頁介面

話說幾年前也寫過一篇筆記 [PHP] 使用 PHP built-in web server 及 PHP CodeIgniter framework 。現在則是使用的場景不同了,在筆記一下使用的情境。

不少嵌入式產品的 UI 是採用 Web 實作的,近幾年則是趨向於 App UI 。然而開發 embedded linux 產品時,其 Web UI 也是打包到 embedded linux 裡頭,這時要開發測時,就滿不方便。若只是單純改改裡頭 js code 則可以免強靠 Browser 做 JS Inject 來達成。

目前協助擴展嵌入式產品的 Web UI 維護團隊,過去曾嘗試把裡頭的 Web UI 使用 Vue.js 跟 webpack 打包機制,弄出個 webpack 自起 Web Server 並透過 devServer.proxy 把 CGI request 導向到實體 device ip。目前因為沒有啟用 Vue.js 所以也沒啟用 webpack 等機制,暫時就先用簡單的 PHP 內置Web Server 機制

$ php -S localhost:8000 -t PATH_DOCUMENT_ROOT

然而,在 fw build code 流程中,可能會有多處檔案搬移的設計,以 Web 來說,大概可以硬搞成 /js/main.js 其實擺在 /tmp/path1/js 中,而 /css/main.css 擺在 /tmp/path2/css 等等,這時就要靠 使用路由(Router)脚本 來處理

$ cat routing.php
<?php

$URL_PATH = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

if (!strncmp($URL_PATH, '/js/', 4)) {
$file_path = '/tmp/path1/js' . $URL_PATH;
if (file_exists($file_path)) {
echo file_get_contents($file_path);
return true;
}
}

if (!strncmp($URL_PATH, '/css/', 4)) {
$file_path = '/tmp/path2/css' . $URL_PATH;
if (file_exists($file_path)) {
echo file_get_contents($file_path);
return true;
}
}
return false;

使用:

$ php -S localhost:8000 -t /tmp routing.php

如此,若有一包 fw code 時,可透過 routing 機制指定某些檔案要從哪裡取得(例如需要 patch 的檔案等),因而避開 build code 後才能測試,便方便許多。另外,還須多補寫一下 /cgi-bin/ 的部分,在把 request 改發到對應的裝置上,如此在面對不需改動 CGI 問題時,可以輕鬆測試 Web UI。

另外,該 routing.php 也可以很結構化:

$ cat routing.php
<?php

function log_info($message) {
$stderr = fopen('php://stderr', 'w');
fprintf($stderr, '=> '.$message."\n");
fclose($stderr);
}
function tree_files( $input_file ) {
if (!file_exists($input_file))
return array();

if (is_dir($input_file)) {
$output = array();
if ($handle = opendir($input_file)) {
while (false !== ($file = readdir($handle))) {
if ($file == '.' || $file == '..')
continue;
$path = $input_file . '/' . $file;
if (!file_exists($path))
continue;
if (is_dir($path)) {
$sub_output = tree_files( $path );
foreach($sub_output as $f) {
array_push($output, $f);
}
} else {
array_push($output, $path);
}
}
closedir($handle);
}
return $output;
}
return array( $input_file );
}
$request_handler = array(
'routing' => array(
'file' => array(
'/index.html' => '/tmp/study/haha.html',
'/world.html' => '/tmp/study/hello/',
),
'dir' => array(
'/js/' => '/tmp/study',
'/css/' => '/tmp/study',
),
),
'content_type' => array(
'html' => 'text/html',
'htm' => 'text/html',
'css' => 'text/css',
'js' => 'text/javascript',
'svg' => 'image/svg+xml',
),
);

$URL_PATH = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path_parts = pathinfo($URL_PATH);

log_info('routing-init, URL_PATH:['.$URL_PATH.'], REQUEST_URI:['.$_SERVER['REQUEST_URI'].']');
log_info('in routing-file mode');
foreach($request_handler['routing']['file'] as $pattern => $target ) {
if (!strncmp($URL_PATH, $pattern, strlen($pattern))) {
log_info('in routing-file mode: match pattern: ['.$pattern.'], target: ['.$target.']');
$path = is_dir($target) ? $target . $URL_PATH : $target;
if (file_exists($path)) {
$path_parts = pathinfo($URL_PATH);
$content_type = isset($request_handler['content_type'][$path_parts['extension']]) ? $request_handler['content_type'][$path_parts['extension']]: mime_content_type($path);

header('Content-Type: ' . $content_type);
$size = filesize($path);
header('Content-Length: '.$size);
echo file_get_contents($path);
log_info('routing-file, response: content-type:['.$content_type.'], content-length:['.$size.'], path:['.$path.']');
return true;
}
}
}
log_info('in routing-dir mode');
foreach($request_handler['routing']['dir'] as $pattern => $target ) {
if (!strncmp($URL_PATH, $pattern, strlen($pattern))) {
log_info('in routing-dir mode: match pattern: ['.$pattern.'], target: ['.$target.']');
$path = $target.$URL_PATH;
if (file_exists($path)) {
$path_parts = pathinfo($URL_PATH);
$content_type = isset($request_handler['content_type'][$path_parts['extension']]) ? $request_handler['content_type'][$path_parts['extension']]: mime_content_type($path);

header('Content-Type: ' . $content_type);
$size = filesize($path);
header('Content-Length: '.$size);
echo file_get_contents($path);
log_info('routing-dir, response: content-type:['.$content_type.'], content-length:['.$size.'], path:['.$path.']');
return true;
}
}
}

return false;

如此一來,在 /tmp/study 又建立一個檔案結構:

% tree /tmp/study 
/tmp/study
├── css
│   └── main.css
├── haha.html
├── hello
│   └── world.html
└── js
    └── main.js

3 directories, 4 files

使用 php -S localhost:8000 routing.php 時,就會一些目錄切換的對應機制。

沒有留言:

張貼留言