Raku(Perl6)のCroを使ってPerl入学式の課題を解いてみる

こんにちは。最近は雇用契約を結ぶために東京と沖縄を行き来しているid:anatofuzです。

これはPerlアドベントカレンダー6日目の記事です。

そういえばYAPC::Kyotoのテーマが決定したようです。 個人的にQといえばウルトラQなのですが、Qにちなんで、今回は旧Perl6ことRakuの話です。

大事な話

Perl6はRakuに名前が代わりました (#rakulangです)

Croを使ってWAFの簡単な例題を解いてみる

さてRakuでWebAppを作ってみたいと、インターネットエンジニアの方は夜中に唐突に思い出して夜も眠れなくなるかと思います。 このエントリでは、Rakuの主力なWAFの一つであるCroを用いてWebアプリケーションを作製したいと思います。

今回作製したコードは次のリポジトリに置いてあります。

github.com

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です。

[GitHub] mojo_prof.pl

` @@ 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 というパッケージの情報をまとめたjsondepends 配列の中身を利用するモジュールと判断し、 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 してみると

f:id:anatofuz:20191206164554j:plain

上のような表示が出ます!! やったね!!!

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] };
        }
    }
}

上のようになります。ここまで出来ると... f:id:anatofuz:20191206165000j:plain

この様にテンプレートエンジンでFizzBuzzが書けます!! やったね!!!

ここまでで、Perl入学式の課題をいくつかCroを使って解いてみました。 続きは別のアドベントカレンダーでまた書くかも...!?

明日は id:kfly8 さんです。わくわく