ルモーリン

Mojoliciousに1個のファイルでサービス追加

投稿:2022-02-26

自分のサイトでは、イタズラでサービスを追加しています。 1個のサービスを追加するために修正や追加するファイルが色々とあります。 複数のファイルを並行して修正、チェックがあったりして面倒くさい。 単一ファイルに全部入れてしまいたい。

例えば、exampleというサービスを追加するケースを考えてみましょう。 ファイルを4ヵ所ほど修正することになります。 この記事は1個の新規ファイルを作成するだけで、これら全てをやろうというものです。

  1. lib/Myapp.pmにルーターを設定する処理「/example/exampleがpostで呼ばれたらApiExampleへ」を追加
  2. lib/Myapp/controller/ApiExample.pmを追加して処理を書く
  3. templates/example.html.ep
  4. draft/service/example.html

あ、draftは私が作った奴なのでノーカンでお願いします。

普通に考えると、単にファイルを追加しただけでは呼び出し側に呼び出しのコードを追加しなくてはなりません。 でもMojoliciousはプラグインの自動登録処理があるので、プラグインのふりをすればとりあえず呼んでもらえます。 アプリケーションのスケルトン生成で自動登録処理が入ります。 自作のプラグインは別ディレクトリにあるので、スケルトンとは別に自動登録処理を足してあります。 自作のをごっちゃまぜにしたい人はスケルトンのプラグイン登録処理で参照されるディレクトリに入れておこう。

	# lib/Myapp.pmのstartupの中

        # 自家製プラグインをロード
        push @{$self->plugins->namespaces}, "Myapp::Plugin";

        # Router
        my $r = $self->routes;

        # アルファベット順なのでログのエンコード出力プラグインが最初にロードされる
        $self->log->debug("----------");
        $self->log->debug("load plugin:");
        my $is_encode = "plugin";
        for (sort $self->home->child("plugin")->list_tree->grep(qr/\.pm$/)->each) {
                my $pluginname = $_->basename;

                $pluginname =~ s/\.pm$//i;
                $self->log->debug("$is_encode: $pluginname") if $config->{pluginlist};

		# ここでプラグインの登録処理を呼ぶ
                $self->plugin($pluginname);

                $is_encode = "プラグイン";
        }

自家製プラグイン用ディレクトリpluginに追加します。 上記の処理でもって再起動時にこのプラグインがロードされてregisterが呼ばれます。 ファイルのパスは「plugin/Example.pm」です。

package Example;
use Mojo::Base "Mojolicious::Plugin";

sub register {
        my ($self, $app, $conf) = @_;

	# ここに実行したい処理を入れる
}

1;

クライアントからサーバーに要求が来た際のルーティングです。 コードのファイル名と無関係な名前を設定できます。 この例では、クライアントから「/example」へpostした場合に関数exampleを呼び出すように設定しました。 postだけ設定したのでクライアントがgetした場合はexampleを呼ばずに「404 Not Found」を返すことに注意してください。

package Example;
use Mojo::Base "Mojolicious::Plugin";

sub register {
        my ($self, $app, $conf) = @_;

	# ここに実行したい処理を入れる

        my $r = $app->routes;
        $r->post("/example")->to(cb => \&example);
}

1;

クライアントがpostした場合の処理をexampleに書きます。 コールバックで呼ばれますが、第1引数(たぶん$self)はコントローラーのオブジェクトですからコントローラーとして書けます(ん?)。

package Example;
use Mojo::Base "Mojolicious::Plugin";

sub register {
        my ($self, $app, $conf) = @_;

	# ここに実行したい処理を入れる

        my $r = $app->routes;
        $r->post("/example")->to(cb => \&example);
}

sub example {
        my $self = shift;

	# クライアントから来る
        my $upload = $self->param("example_file");

	# 処理をする

	# 応答
	$self->render(text => "処理しました。");
}

1;

処理だけでなく最初のページも要りますよね。 HTMLファイルっぽいURLがリクエストされたら初期画面のHTMLを返します。 開発版でデバッグをした際に画面で見分けられるようにしてあります。 あとサンプルコードのハイライトがPerlの中にHTMLを埋めているので収拾がつきません(笑)。

package Example;
use Mojo::Base "Mojolicious::Plugin";

sub register {
        my ($self, $app, $conf) = @_;

	# ここに実行したい処理を入れる

        my $r = $app->routes;
        $r->post("/example")->to(cb => \&example);
	$r->get("/example/index.html")->to(cb => \&example_index);
}

sub example {
        my $self = shift;

	# クライアントから来る
        my $upload = $self->param("example_file");

	# 処理をする

	# 応答
	$self->render(text => "処理しました。");
}

sub example_index {
        my $self = shift;

        my $title = "サンプルのサービス";
        $title .= "(開発版)" if "development" eq $self->app->mode;

	my $html = <<EOF;
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>$title</title>
</head>
<body>
$title<br />
<br />
<form action="/example" method="post" enctype="multipart/form-data">
ファイル:<input type="file" name="example_file" accept=".txt" required><br />
<input type="submit" value="アップロード"><br />
</form>
</body>
</html>
EOF

	return $self->render(text => $html);
}

1;