顯示具有 web server 標籤的文章。 顯示所有文章
顯示具有 web server 標籤的文章。 顯示所有文章

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 時,就會一些目錄切換的對應機制。

2016年3月4日 星期五

設定 Nginx 透過 proxy_pass 存取 Apache web server + SVN + DAV 服務 @ Ubuntu 14.04

這個故事是這樣的,當所有的 server deploy 都用預設用 nginx 後,有個成員跳出來說要用 SVN over HTTP 服務時,就發現必須架設 Apache Web server ,因為 SVN over HTTP 的相關模組就只有 Apache Web server 才有,所以就變成此況的窘境了。

總之,架設 Apache web server 很容易,而安裝 mod_dav_svn 也差不多:

$ sudo apt-get install subversion libapache2-mod-svn apache2
$ sudo vim /etc/apache2/ports.conf
從 Listen 80 改成 Listen 8000,避開跟 Nginx 預設 80 port 衝到
$ sudo vim /etc/apache2/sites-available/000-default.conf
從 <VirtualHost *:80> 改成 <VirtualHost *:8000>
$ sudo a2enmod dav_svn authz_svn
$ sudo vim /etc/apache2/conf-available/svn.conf
<Location /svn>
        DAV svn
        SVNParentPath /path/svn
        AuthzSVNAccessFile /path/svn_access
        AuthType Basic
        AuthName "Web svn"
        AuthUserFile /path/svn_auth
        Require valid-user
</Location>
$ sudo service apache2 restart


接著設定 Nginx:

location /svn/ {
proxy_pass http://127.0.0.1:8000/svn/;
}

# 更新:假設 svn repo 裡有 .ht 開頭的檔案,可能會被 nginx 條件擋下
location ~ /svn/.*\.ht {
proxy_pass http://127.0.0.1:8000/;
}


如此一來即可搞定。

2013年11月7日 星期四

[Python] Bottle + Nginx + uWSGI @ Ubuntu 12.04

環境:

$ sudo apt-get install python-bottle nginx uwsgi uwsgi-plugin-python

Python Bottle 範例 (/var/www/bottle_app):

$ vim /var/www/bottle_app/app.py
from bottle import Bottle, route, run, install, template, static_file
#from bottle_sqlite import SQLitePlugin

#import bottle
#application = bottle.default_app()

app = application = Bottle()
#app.install(SQLitePlugin(dbfile='/tmp/test.db'))

@app.route('/')
def index():
        return template('Hello world')

if __name__ == '__main__':
        run(app, host='0.0.0.0', port=1234)


設定 uWSGI:

$ sudo vim /etc/uwsgi/apps-available/bottle_app.ini
[uwsgi]
socket = /run/uwsgi/app/bottle_app/socket
chdir = /var/www/bottle_app
master = true
plugins = python
file = app.py
uid = www-data
gid = www-data
$ sudo ln -s /etc/uwsgi/apps-available/bottle_app.ini /etc/uwsgi/apps-enabled/bottle_app.ini
$ sudo service uwsgi restart


設定 nginx:

$ sudo vim /etc/nginx/sites-available/bottle_app
upstream _bottle {
server unix:/run/uwsgi/app/bottle_app/socket;
}

server {
listen 8080;
root /var/www/bottle_app;

location / {
try_files $uri @uwsgi;
}

location @uwsgi {
include uwsgi_params;
uwsgi_pass _bottle;
}
}

$ sudo ln -s /etc/nginx/sites-available/bottle_app /etc/nginx/sites-enabled/bottle_app
$ sudo service nginx restart


其他部分,對於 /var/www/bottle_app/app.py 留意 application 變數是 uwsgi 尋找的對象,若想要改變可以在 uwsgi 定義 callable (預設 callable = application)。

至於使用 bottle 開發上麻煩的地方是 templates(views) 更新後,必須重新啟動才能生效,就算改用 uwsgi 也沒有改善,不知是不是少設定甚麼,或是預設規劃就是如此?

除此之外,若不用那麼著重效率,還可以替換掉 Bottle 預設的 wsgiref WSGIServer (non-threading HTTP server),而且改法很簡單(switching-the-server-backend):

$ sudo apt-get install python-paste
$ vim app.py
...
if __name__ == '__main__':
import paste
run(
app ,
host='0.0.0.0',
port=1234,
server='paste'
)