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のそれでも調べてみればいいのか。