cgi
pico-args
rustのexampleに、 引数のパース なんてのが掲載されてる。が、もう少し手軽にならないか。ってんで、前回見付ておいた、pico-argsなんてのを試してみる。
Running `target/debug/zpiko --number 123 --opt-number 987 --output myoutput ZIN`
Args { inner: ["target/debug/zpiko", "--number", "123", "--opt-number", "987", "
--output", "myoutput", "ZIN"] }
AppArgs {
number: 123,
opt_number: Some(
987,
),
width: 10,
input: "ZIN",
output: Some(
"myoutput",
),
}
下記は例にあったのに些細な間違えがあったので、ちょいと訂正してる。
struct AppArgs {
number: u32,
opt_number: Option<u32>,
width: u32,
input: std::path::PathBuf,
output: Option<std::path::PathBuf>,
}
fn parse_args() -> Result<AppArgs, pico_args::Error> {
let mut pargs = pico_args::Arguments::from_env();
:
let args = AppArgs {
number: pargs.value_from_str("--number")?,
opt_number: pargs.opt_value_from_str("--opt-number")?,
width: pargs.opt_value_from_fn("--width", parse_width)?.unwrap_or(10),
output: pargs.opt_value_from_os_str("--output", parse_path)?,
input: pargs.free_from_str()?,
};
fn main() {
let xxxx = std::env::args();
println!("{:?}", xxxx);
let args = match parse_args() {
Ok(v) => v,
Err(e) => {
eprintln!("myError: {}.", e);
std::process::exit(1);
}
};
println!("{:#?}", args);
}
引数を全て、構造体に押し込めてしまうとな。して、メンバーを埋めていく時に、解析の順番で必須引数とオプション引数を使い分ているのか。これぐらいが手頃でいいな。
git版には、
ob$ ls test-apps/ argh-app/ gumdrop-app/ pico-args-app/ clap-app/ null-app/ structopt-app/
有名なclap(拍手)を始めその派生版のstructoptとかの定義比べがでてる。けど、たかがパースするだけで、とんでもない数の木箱を使うって、将来に悔過を残さないか? 気の小さいオイラーは、心配になっちゃうぞ。
Web server
以前やったsqlite3の英日辞書をWEBからアクセスしてみたい。今迄便利に使っていたPythonの簡易サーバー python3 -m http.server 8080 は、残念ながら使えない。だって、バックエンドでsqliteを使う必要があるからだ。
そこで、アラウンドY2K(2000年前後)時代に使ってた、本物のWEBサーバーが必要。あの頃は、apaache 1.3.XXを使ってたな。2.X時代になった頃には、足を洗ってしまったから、20年もブランクがある。
今なら、nginxが主流? それともapacheが健在? OpenBSDのportsを調べてみたら、nginx 1.18.0, apache 2.4.46 なんてのが提供されてた。luaがサポートされてたりLDAPが追加されたりして、隔世の感があるなあ。
仮にapacheを使うとして、cgiをやるには、面倒な設定が必要だったはず。手元に何か資料がないものかと探してみたら、Gauche本で、cgiの要件が説明されてた。
mod_cgi.so をロードして、cgiを使えるサーバーにしろ。拡張子 .cgi で、cgiスクリプトだと認識させろ。それからWEBサーバーからgoshが起動出来るようになってる亊、ですってさ。
面倒なこった。で、続きを読むと、text.html-liteとかwww.cgiを使ってくださいなんて説明があった。残念ながらsqliteへのアクセスモジュールは標準提供されていないようだ。GaucheでRDBプログラミングに解説が出てたぞ。また、括弧まぎれになってみるか?
そんな亊より問題が。Gauche download にあるgitの案内では、いつの間にかソースが取れなくなっている。
ob$ git clone git://github.com/shirok/Gauche.git Cloning into 'Gauche'... fatal: remote error: The unauthenticated git protocol on port 9418 is no longer supported. Please see https://github.blog/2021-09-01-improving-git-protocol-security-github/ for more information.
案内をみると、時代が変っていたぞ。
ob$ git clone https://github.com/shirok/Gauche.git
これでOK。既に利用中ならば、下記のように修整すればよい。
sakae@deb:~/src/Gauche$ cat .git/config
:
[remote "origin"]
url = https://github.com/shirok/Gauche.git
fetch = +refs/heads/*:refs/remotes/origin/*
で、昔の亊を思い出した。これって年寄の特技です。OpenBSDには備付の家具ならぬ、備付のWEBサーバーが用意されてるじゃん。2018年に、根ほり葉ほり調べているぞ。 OpenBSD httpd
折角あるなら有効活用しよう。既に常人の道を外れています。
static?
で、問題になりそうなのが、セキュリティーに五月蝿いOpenBSDの常で、WEBエリアはjail環境になってる亊。FreeBSDでもjailを売りにしてる。jailって刑務所の亊、世間一般から隔離されてるのよ。いわゆるサンドボックス。unix言葉で言ったらchrootね。
これの何が問題かと言うと、アプリが必要とするライブラリィーをOSが用意した物と共有出来無いって亊。だから、アプリは必要なライブラリィーを全て組み込んだスタチックな物が欲しい。
そんな都合良くrustが振る舞ってくれるか。ええ、C言語に変ってこの所ずっとrustですからな。軽く調べてみると、
RustのLinux muslターゲット (その1:Linux向けのポータブルなバイナリを作る)
Go と Rust における静的リンクのビルド方法(+ Dockerfile サンプル)
どうも、特殊な亊をやらないと無理みたい。世間一般に流通してるリナなら、何とかなりそうだけど、OpenBSDを選んだ時点に荒野に放り出されたと覚悟ですよ。
まあ、野生の証明ですな。最近、森村誠一さんの、野生の証明を読んだ所だから、ぴったりなのさ。
昔の設定をちょっと変えて、動くか試運転。
vbox$ cat /etc/httpd.conf
ext_ip = "0.0.0.0"
prefork 1
server "default" {
listen on $ext_ip port 8080
root "/htdocs/"
location "/cgi-bin/*" {
fastcgi socket "/run/slowcgi.sock"
root "/"
}
}
types {
include "/usr/share/misc/mime.types"
}
httpd.confを設定して起動するだけ。pythonサーバーに習って、8080で待受。スタチックエリアは、htdocs以下。動的アプリはcgi-binの下に配置するっていう、簡単明瞭な設定。slowcgiで、昔風のcgiをサポートすると言う、Y2K時代の再現です。
尚、WEBエリアのトップdirは、固定で var/www になってる。
cgi and html
昔の亊ですっかり忘れているので、資料収集。
フォームからの入力を受け取る basic by perl
それから、結果表示の為の知恵を少し仕入れた。
<dl>
<dt>りんご</dt>
<dd>赤い色をした丸い果物。</dd>
<dt>バナナ</dt>
<dd>黄色い色をした細長い果物。</dd>
</dl>
りんご
赤い色をした丸い果物。
バナナ
黄色い色をした細長い果物。
バックエンドの試作
バックエンドとして、検索ワードが渡ってきた時、辞書ファイルを検索してhtmlを出力するのを作成する。
use sqlite::Value;
fn search(sw: &str) {
let ew = format!("{}{}", sw, "%");
let connection = sqlite::open("ejdict.sqlite3").unwrap();
let mut cursor = connection
.prepare("SELECT word,mean FROM items WHERE word like ? limit 3")
.unwrap()
.into_cursor();
cursor.bind(&[Value::String(ew.to_string())]).unwrap();
println!("Content-type: text/html; charset=UTF-8\n");
while let Some(row) = cursor.next().unwrap() {
println!("<dt>{}</dt>", row[0].as_string().unwrap());
println!("<dd>{}</dd>", row[1].as_string().unwrap());
}
}
fn main(){
let sw = "rust";
search(sw);
}
面倒そうなcgiデータのパースをサボった版だ。検索ワードに '%' を追加してるのは、検索ワード* っていう正規表現のsql版だ。
配置と試運転
cgi-bin/sqlを駆動する為の入力用htmlファイルを作る。場所がhtdocs/ej/ の下にしてるのは、有名なトリックだ。こうすると、面倒なってか無様な長ったらしいURLを入力しなくてもよくなる。WEBサーバーのお約束として、http://localhost/ej と指定するだけで、自動でej/の中にあるindex.htmlが参照される。
vbox$ cat /var/www/htdocs/ej/index.html <html><head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </head><body> English word to Japanese <br><br> <form method=POST action="/cgi-bin/sql"> <input type="text" name="ew"> <br><br> <input type="submit" value="trans"> </form></body></html>
次はバックエンドのsqlの確認。
vbox$ ldd target/debug/sql
target/debug/sql:
Start End Type Open Ref GrpRef Name
167c6000 367c9000 exe 2 0 0 target/debug/sql
0ec30000 2ec35000 rlib 0 1 0 /usr/local/lib/libsqlite3.so.37.13
00afc000 20afe000 rlib 0 2 0 /usr/lib/libpthread.so.26.1
054bf000 254c3000 rlib 0 1 0 /usr/lib/libc++abi.so.5.0
043e0000 243f1000 rlib 0 1 0 /usr/lib/libc.so.96.1
0b16b000 2b16d000 rlib 0 1 0 /usr/lib/libz.so.6.0
0bc93000 2bc95000 rlib 0 1 0 /usr/lib/libm.so.10.1
0734a000 0734a000 ld.so 0 1 0 /usr/libexec/ld.so
必要なライブラリィー類を、/var/www/usr/ 以下へコピーする。まあ二重生活になるけど我慢。usr以下は6.5Mになった。それから、大事なsqlと辞書ファイルを/var/www/cgi-bin以下に配置する。本来なら、辞書ファイルはWEBからアクセス出来るエリアを避けて/var/www/usr/share/dictあたりに配置するのが正しい処置だけど、大目にみてね。
これで準備が整ったんで、http://localhost/ej へアクセス。 がーん、 500 internal error エラーですって。こういう時はエラーログの確認だな。
vbox# cat logs/error.log ld.so: sql: can't load library 'libsqlite3.so.37.13'
しばし考える。ライブラリィーが見付からない。 LD_LIBRARY_PATH の出番か? それらしいコマンドの
ldconfig を見付た。キャッシュが必要なのね。本来はldconfigするのが正しい処置だろうけど、本体OS側で使っているものを流用させて貰う亊にする。
vbox# cd /var/www vbox# mkdir -p var/run vbox# cd var/run/ vbox# cp /var/run/ld.so.hints .
w3mを使っても、ちゃんと機能した
English word to Japanese [ ] [trans]
結果画面
rust
(金属の)『さび』 / さび病;さび菌 / さび色 / さびる / 〈能力などが〉(使わず
に)鈍る / 〈植物が〉さび病にかかる / 〈金属〉‘を'さびさせる / さび色の
rustic
『いなか』(『田舎』)『の』,田舎ふうの,田園生活の(rural) / 《良い意味で》素
ぼくな,飾り気のない(simple);《しばしば軽べつして》田舎者の,田舎っぽい / 《
名詞の前にのみ用いて》粗木(あらき)作りの,丸木作りの / 《しばしば軽べつして
》田舎者,農夫
rusticate
《おもに文》…‘を'田舎へ行かせる / …‘を'田舎ふうにする / 《英》〈大学生〉‘を
'停学処分する / 《おもに文》田舎へ行く / 田舎ふうになる
なお、libsqlite3.soは、pythonに連られて自動でインストールされてる。OpenBSDでは、何故か、OS側にあるやつが使われた。何故かは深くは追求しない。
get and post
ブラウザーから送られるデータをcgiアプリケーションに届ける方法として、 入力フォーム編(GETとPOSTデータを受け取るには) のように2種類がある。英語に詳しくなくても、get/postって真逆だろう。getは、サーバーからファイルを取ってくる。そんなに長大なデータを想定してないので環境変数で渡してしまえ。初期のcgiはこの考えだな。
そのうちにファイルをアップロードしたいって要求が生れた。だからサーバーにpostするって命令語が選ばれた。当然、長大なものになると予想されるので、環境変数に載せるんでは役たたず。サーバー側では標準入力からのストリーミングで受けて立つって塩梅。
POST対応限定のコードを書いていこう。登場人物はハッシュになるので、少し調べておく。
そして、出来上がったもの。標準入力からデータを読むので stdin().read_line するんだけど、こやつ最後の改行も含めて取得されちゃう。だから、pop()で、この改行を削除してる。
無闇に削除するんじゃなくて、 trim_end() を使うのが本当は正解。行末にホワイトスペースが有ると削除してくれる。
で、実は重大なCGIの規格違反をしている。perl言語で書かれたものは、 read (STDIN, $PostData, $ENV{'CONTENT_LENGTH'}); の様に、指定されたサイズだけ読み込んでいる。が、面倒なので誤魔化している。それでも、firefox,chroneでは動いた。w3mは駄目だった。本当は、takeとかを使うんだろうね。
use std::collections::HashMap;
fn main() {
let mut cgiin = String::new();
std::io::stdin().read_line(&mut cgiin).unwrap();
// cgiin.pop(); // delete '\n'
let mut pd = HashMap::new();
let ary: Vec<&str> = cgiin.split('&').collect();
for el in ary {
let kv: Vec<&str> = el.split('=').collect();
pd.insert(kv[0], kv[1]);
}
let sw = pd.get("ew").unwrap();
search(sw);
}
オフライン試験するなら
vbox$ echo 'name=Rich&ew=rust&state=KY&side=Monkey' | target/debug/sql
いちいち、出来たアプリを転送するの面倒くさいからね。
perlの真似は大変だ
上で出て来た不満を解消する手立て。
これは取りも直さず、perlで実現してる read (STDIN, $PostData, $ENV{'CONTENT_LENGTH'}) を実現する亊だ。
Y2Kの時代はperlしかなくて、そこへrubyが殴り込みをかけたんだな。pythonなんて片鱗もなかったぞ。オイラーはrubyの物珍しさに引かれて使っていたけどね。
時代が変って、先進のrustでやったら、どうなるかって試み。
バイト配列を文字列に変換する方法を事前に調べておく。 Rustでバイト列から文字列へ
fn main() {
let bytes = vec![0x41, 0x42, 0x43];
let s = format!("{:?}", &bytes);
println!("{}", s);
}
こんな技も有るんだね。
use std::io::Read;
fn main() {
let cs: u64 = std::env::var("CONTENT_LENGTH").unwrap().parse().unwrap();
let len = 512;
let mut buf = Vec::<u8>::with_capacity(len);
unsafe {
buf.set_len(cs as usize);
}
let f = std::io::stdin();
let mut handle = f.take(cs);
handle.read(&mut buf).unwrap();
println!("{:?}", buf);
let cgiin: String = String::from_utf8(buf.to_vec()).unwrap();
println!("{:?}", cgiin);
}
環境変数を設定してから、実行。
sakae@pen:/tmp/cgi$ export CONTENT_LENGTH=5
sakae@pen:/tmp/cgi$ cargo r
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/cgi`
123456789
[49, 50, 51, 52, 53]
"12345"
確かに指定した長さだけ、取り込まれているな。こういう需要は、余りないのかな?
どうやらOKなんで、前記のやつと組み合わせたら、今度はちゃんとw3mからもアクセスできた。 これにて、目的達成!! clap clap と喜んで拍手しつつ、辞書を引いてみたら、平手打ちって意味も載ってた。かしわでで変換したら柏手になった。そういうものなんですか? 金田一先生。
by golang
掟破りで、golangに触ってみる。
5.3 SQLiteデータベースの使用 sqlite3で調べていたら、もっと広い枠であるWEBサーバーにも言及してる本に出会った。グーグル流のモダンなやつって認識でいいのかな? それとも、これがY2K時代のcgiから脱却する標準的な枠組みなのかな。
vbox$ cd zz
vbox$ go mod init
go: cannot determine module path for source directory /tmp/zz (outside GOPATH, m
odule path must be specified)
Example usage:
'go mod init example.com/m' to initialize a v0 or v1 module
'go mod init example.com/m/v2' to initialize a v2 module
Run 'go help mod init' for more information.
オイラーの大好きな、/tmpの下でゴニョゴニョするのは、拒否された。
vbox$ cd go/ vbox$ mkdir -p src/gosql vbox$ cd src/gosql/ vbox$ go mod init go: creating new go.mod: module gosql
由緒正しく、所定の場所に店を作れとな。面倒っぽいな。
vbox$ go build
main.go:7:5: no required module provides package github.com/mattn/go-sqlite3; to
add it:
go get github.com/mattn/go-sqlite3
vbox$ go get github.com/mattn/go-sqlite3
go: downloading github.com/mattn/go-sqlite3 v1.14.12
go get: added github.com/mattn/go-sqlite3 v1.14.12
vbox$ go build
vbox$ ldd gosql
gosql:
Start End Type Open Ref GrpRef Name
00400000 20033000 exe 2 0 0 gosql
09b7a000 29b7c000 rlib 0 1 0 /usr/lib/libpthread.so.26.1
0fd24000 2fd35000 rlib 0 1 0 /usr/lib/libc.so.96.1
0e8ee000 0e8ee000 ld.so 0 1 0 /usr/libexec/ld.so
vbox$ ls -lh gosql
-rwxr-xr-x 1 sakae wheel 9.4M Apr 10 06:11 gosql*
こちらは、libsqlを内蔵してるね。そんなものなのか。
そうだ、golangにこだわる亊なく、rubyのそれでも調べてみればいいのか。