こんにちは。最近は雇用契約を結ぶために東京と沖縄を行き来しているid:anatofuzです。
これはPerlアドベントカレンダー6日目の記事です。
そういえばYAPC::Kyotoのテーマが決定したようです。 個人的にQといえばウルトラQなのですが、Qにちなんで、今回は旧Perl6ことRakuの話です。
大事な話
Perl6はRakuに名前が代わりました
(#rakulang
です)
Croを使ってWAFの簡単な例題を解いてみる
さてRakuでWebAppを作ってみたいと、インターネットエンジニアの方は夜中に唐突に思い出して夜も眠れなくなるかと思います。 このエントリでは、Rakuの主力なWAFの一つであるCroを用いてWebアプリケーションを作製したいと思います。
今回作製したコードは次のリポジトリに置いてあります。
Croとは
RakuのIDEであるCommaを開発しているチームが主に開発しているWAFです。
厳密にはWAFではなく、reactive programmingのためのフレームワークという立ち位置です。
その為、デフォルトパッケージである Cro
はルーティングやweb socketなどをサポートしていますが、テンプレートエンジンなどは派生パッケージである Cro::WebApp
名前空間のものを利用するとなっています。
Croのインストール
まずはCroのインストールから始めてみましょう。今回はDockerで色々しますが、何かと物があったほうが便利です。
Raku自体は $brew install rakudo-star
などで入れましょう
$zef install cro
なお $zef install Cro
だと、別のパッケージが入るので気をつけましょう。
....ですが、2019/12/06現在 なんか IO::Socket::Async::SSL
周りで死ぬので諦めたほうが良いかもしれません....orz
$ zef install cro ===> Searching for: cro ===> Searching for missing dependencies: IO::Socket::Async::SSL, YAMLish, Cro::WebSocket, Docker::File, File::Ignore ===> Searching for missing dependencies: Cro::HTTP, Base64, Digest::SHA1::Native, Crypt::Random ===> Searching for missing dependencies: IO::Path::ChildSecure, HTTP::HPACK, Cro::TLS, JSON::JWT, Log::Timeline, if ===> Searching for missing dependencies: Digest::HMAC ===> Building: Digest::SHA1::Native:ver<0.04> ===> Building [OK] for Digest::SHA1::Native:ver<0.04> ===> Testing: IO::Socket::Async::SSL:ver<0.7.5> ===> Testing [FAIL]: IO::Socket::Async::SSL:ver<0.7.5> Aborting due to test failure: IO::Socket::Async::SSL:ver<0.7.5> (use --force-test to override)
モックを作ろう
ではまずプロジェクトを始めるために適当にディレクトリを作ります
$mkdir test_cro
croをいれるとcroコマンドが使えるようになるので、これでstubを作成します。 croが上手く入らなかった場合は、同じファイルを頑張って手で書けば問題ないです。
$cro stub http entrance .
今回は entrance
というプロジェクトで作成します。
作製すると次のようなディレクトリ構成のものが出来上がります
. ├── Dockerfile ├── META6.json ├── README.md ├── lib │ └── Routes.pm6 ├── service.p6
入学式ではMojolicious::Liteを使ってエイッとしていましたが、今回はほぼこの雛形のRouters部分を編集することになります。
Perl入学式の課題1
Perl入学式の練習問題ではこのようなものがあります。
先ほど作成したhttp://127.0.0.1:3000/profileを充実させましょう。以下のページからコードをコピペして利用してもokです。
` @@ profile.html.ep % layout 'default'; % title 'profile'; <h1>プロフィール</h1> <p>私の名前は<%= $name %>です</p> <p>趣味は<%= $hobby %>で, 好きなプログラミング言語は<%= $lang %>です</p>
テンプレート部の profile を、コピペして上記のものに置き換えます。 コントローラー内で stash を利用して name, hobby, lang 変数に値を代入します。 思いつかない人用ハッシュ (name => 'larry', hobby => 'study', lang => 'Perl' )
workshop-2019/slide.md at master · perl-entrance-org/workshop-2019 · GitHub
Croのデフォルトは127.0.0.1ではなく0.0.0.0の10000番ポートであるので、適宜組み替えて、これに該当するものを作ってみます。
Dockerで作業をする
....と、まずはcroが今日はmac osでは上手く動かないので、 Dockerを使っていい感じにビルド出来るようにしてみましょう。
FROM croservices/cro-http:0.8.1 RUN mkdir /app COPY . /app WORKDIR /app RUN apt-get update && apt-get -y install build-essential RUN zef install --deps-only . && perl6 -c -Ilib service.p6 ENV ENTRANCE_HOST="0.0.0.0" ENTRANCE_PORT="10000" EXPOSE 10000 CMD perl6 -Ilib service.p6
このようなDockerfileを用意します。 (cro stubでも同様のものができますが、 makeコマンドが無いので RUN apt-get update && apt-get -y install build-essential
を加えます)
次に docker build
するのが面倒なので、 docker-compose.yml
を書きます
version: "3.6" services: app: build: . image: anatofuz_cro_entrance volumes: - .:/app ports: - "10000:10000" tty: true
ここまで書いて docker-compose up
すると、croのweb appが立ち上がると思います。
ルーティングとテンプレートエンジン
ここまで出来たら、次に /profile
のエンドポイントを生成するのと、該当するテンプレートを作ってみましょう。
説明の都合上まずはテンプレートエンジンを導入します。
Perl6/Rakuでは現状 META6.json
というパッケージの情報をまとめたjsonの depends
配列の中身を利用するモジュールと判断し、 zef
がインストールします。
その為、 まずは この META6.json
に利用するパッケージを記述する必要があります。
使用するテンプレートエンジンはCro::WebApp::Template
ですので、以下のように追記します。
"depends": [ "Cro::HTTP", "Cro::WebApp::Template" ],
次にテンプレートファイルを用意します。
テンプレートファイルを置く場所は任意に設定できます。
今回はプロジェクトルートにtemplates
ディレクトリを作製し、その中に書いていきます。
テンプレートファイルの拡張子は .crotmp
が使われている様です。
$mkdir templates $vim profile.crotmp
profile.crotmp
の中身はこんな感じです
<!doctype html> <html lang="ja"> <head> <title> <.<title>> </title> </head> <body> <h1> プロフィール </h1> <p> 私の名前は <.<name>> です</p> <p>趣味は <.<hobby>>で, 好きなプログラミング言語は<.<lang>>です</p> </body> </html>
mojolicousでは、渡した変数を使う場合は <%= $name %>
としましたが、 Croでは無名ハッシュを送り、 <.<key>>
とすることでアクセスできます。
これはRakuのSyntaxに近いものですね。
次にRoutes.pm6を編集します
Routes.pm6は、Croのstubが生成するデフォルトのルーティングファイルであり、 Cro::HTTP::Router
によって実現されています。
Croのルーティングには、 template 機能があり、 レンダリングするテンプレートと無名ハッシュの形でパラメータを送る事ができます。
テンプレートは Cro::WebApp::Template
ですので、 これをuseして template-location
にプロジェクトルートから見たtemplateファイルのパスを指定します。
use Cro::HTTP::Router; use Cro::WebApp::Template; sub routes() is export { template-location 'templates'; route { get -> { template 'index.crotmp', { title => "タイトル", greeting => "hello Cro" }; } get -> 'profile' { template 'profile.crotmp', { title => "タイトル", name => "AnaTofuZ" , hobby => "Twitter", lang => "Perl"}; } } }
この例では /
及び /profile
にGETリクエストが発行された場合、それぞれ index.crotmpと profile.crotmpにパラメータを渡しつつレンダリングをします。
実際にここまで書いて dokcer-compose up
してみると
上のような表示が出ます!! やったね!!!
FizzBuzzをテンプレートエンジンで解く
Perl入学式にはFizzBuzzをテンプレートエンジンで解く課題があります。やってみましょう。
まずは fizzbuzz.crotmpですが
<!doctype html> <html lang="ja"> <head> <title> <.<title>> - Cro </title> </head> <body> <@numbers: $var> <?{$var % 3 == 0}> Fizz </?> <?{$var % 5 == 0}> Buzz </?> <?{($var % 3 != 0) && ($var % 5 != 0 )}> <$var> </?> <br/ > </@> </body> </html>
上の様になります。 Croの templateエンジンでは <@array: $scalar>
と書くと、 </@>
が来るまでの間、 Perlで言うところの for my $scalar (@array)
と書いたのと同じ意味になります。
以下の <? {} > </?>
は, {}
の中身を真偽値判定する if文のようなものであり、 <?
の場合は中がtrueであるときに要素がレンダリングされます。
そしてFizzBuzzに値を送るルーティング部分は
use Cro::HTTP::Router; use Cro::WebApp::Template; sub routes() is export { template-location 'templates'; route { get -> { template 'index.crotmp', { title => "タイトル", greeting => "hello Cro" }; } get -> 'profile' { template 'profile.crotmp', { title => "タイトル", name => "AnaTofuZ" , hobby => "Twitter", lang => "Perl"}; } get -> 'fizzbuzz' { template 'fizzbuzz.crotmp', { title => "xxx", numbers => [(0..100).list] }; } } }
上のようになります。ここまで出来ると...
この様にテンプレートエンジンでFizzBuzzが書けます!! やったね!!!
ここまでで、Perl入学式の課題をいくつかCroを使って解いてみました。 続きは別のアドベントカレンダーでまた書くかも...!?
明日は id:kfly8 さんです。わくわく