ルーティングの基礎

次にルーティングについてもう少し詳しく解説します。ルーティングとはURLと処理の対応の記述のことです。

簡単なルーティングについては、これまでも使ってきました。

get '/' => sub { ... };
get '/info' => sub { ... };
get '/date/:date' => sub { ... };

ここでは、ルーティングについてもう少し詳しく解説します。

プレースホルダー

最初にプレースホルダーについて解説します。

通常のプレースホルダー

先頭にコロンをつけると、その部分はパラメーターとして取得できることは説明しました。

get '/date/:date' => sub {
  my $self = shift;
  my $date = $self->param('date');
};

Mojoliciousでは、これをプレースホルダーと呼びます。注意してほしいのは、このプレースホルダーは、「/」と「.」以外の値にマッチするということです。

たとえば「/date/20131015/hello」というURLでアクセスしたとしましょう。これはルーティングが成功しません。ルーティングが成功して「date」に「20131015/hello」を受け取ることができるのではなくって、ルーティング自体が成功せず、ページが見つかりません。

たとえば「/date/20131015.json」というURLでアクセスしたとしましょう。これはルーティングが成功しません。ルーティングがマッチして「date」に「20131015.json」を受け取ることができるのではなくって、ルーティング自体が成功せず、ページが見つかりません。

プレースホルダーは「()」を使って、その部分がプレースホルダーであることを区別することもできます。

get '/date/(:date).json' => sub { ... };

「/date/20130213.json」でアクセスすれば、ルーティングが成功して、パラメーター「date」に「20130213」を受け取ることができます。

リラックスプレースホルダー

通常のプレースホルダーは「/」と「.」以外にマッチしますが、リラックスプレースホルダーを使えば「.」以外にマッチさせることができます。リラックスプレースホルダーは先頭が「#」ではじまります。

get '/date/#date' => sub { ... };

「/date/20130213.json」でアクセスすれば、ルーティングが成功して、パラメーター「date」に「20130213.json」を受け取ることができます。「.」を含んだ部分を取得することができます。

リラックスプレースホルダーはこのように「.」を含んだ部分もパラメーターとして受け取ることができるのが特徴です。

ワイルドカードプレースホルダー

「/」と「.」を含むすべての文字にマッチさせるにはワイルドカードプレースホルダーを利用します。ワイルドカードプレースホルダーは先頭が「*」で始まります。

get '/date/*date' => sub { ... };

「/date/20130213/hello」でアクセスすれば、ルーティングが成功して、パラメーター「date」に「20130213/hello」を受け取ることができます。「/」を含んだ部分を取得することができます。

ワイルドカードプレースホルダーはこのように「/」を含んだ部分もパラメーターとして受け取ることができるのが特徴です。

HTTPメソッド

次にHTTPメソッドについて解説したいと思います。HTTPメソッドというのは、HTTPリクエストを送信するときに指定するものです。一般的な用法であれば、ページの内容を取得するときはGETメソッド、フォームなどでデータを送信するときはPOSTメソッド、ページの存在を確認したい場合はHEADメソッドが使用されます。

メソッド名 用法
GET ページの内容を取得
POST フォームなどでデータを送信
HEAD ページの存在を確認

他にもPUTメソッドやDELETEメソッドやPATCHメソッドなどがありますが、ここではこの三つを覚えてください。特にGETとPOSTを覚えておけばWebサイトの作成に困ることはありません。

Mojolicious::Liteでは上記のHTTPメソッドに対応して以下の関数が利用できます。

get '/' => sub { ... };
post '/' => sub { ... };
head '/' => sub { ... };
put '/' => sub { ... };
del '/' => sub { ... };
patch '/' => sub { ... };

すべてのHTTPメソッドにマッチさせる

同じURLでgetとpostの両方を受けたいという場合は多いと思います。そのような場合はany関数を使いましょう。すべてのHTTPメソッドにマッチします。

any '/' => sub { ... };

HTTPメソッド名を取得する

any関数で受け取った場合は、ユーザーがどのHTTPメソッドでアクセスしてきたのかを知りたい場合があると思います。そのような場合はreqメソッドでHTTPリクエストを表現するMojo::Message::Requestオブジェクトを取得して、methodメソッドでHTTPメソッドを取得できます。

my $http_method = $c->req->method;

ページが見つからない場合を自分で処理する

日付の場合は、たとえば、数値の8桁でなければ、ページが見つからないという処理を書きたいと考えると思います。数値の8桁以外の場合は、意味を持たないからです。このような場合は、存在しないということを知らせるのがよいでしょう。reply->not_foundを使ってページが「404 Not Found」を自分で描画することができます。

get '/date/:date' => sub {
  my $self = shift;
  my $date = $self->param('date');
  
  # 日付の形式でない場合は「404 Not Found」を描画する
  unless ($date =~ /^[0-9]{8}$/) {
    $self->reply->not_found;
    return;
  }

  $self->render('date', date => $date);
};

reply->exceptionを代わりに使えば、エラーを示す「500 エラーメッセージ」を表示することもできます。

$c->reply->exception('Error');

すべてのルーティングに共通する前処理

すべてのルーティングに共通する前処理を書きたいと思うことがあると思います。たとえば、ログインしていなければ「/login」というページに、リダイレクトしたい場合があると思います。

このような場合はunder関数を使えば、すべてのルーティングに共通する前処理を書くことができます。以下のサンプルではunder関数に渡したサブルーチンの中でスタッシュの値を取得して、それをふたつの異なるルーティング「/some1」と「some2」で利用できることを示しています。

use Mojolicious::Lite;

# 前処理
under sub {
  my $self = shift;
  
  $self->stash('name' => 'Kimoto');

  return 1;
};

get '/some1' => sub { 
  my $self = shift;

  my $name = $self->stash('name');

  $self->render(text => $name);
};

get '/some2' => sub { 
  my $self = shift;

  my $name = $self->stash('name');

  $self->render(text => $name);
};

app->start;

上記のサンプルではunder関数の中で「return 1」として1を返していますが、これは後に続く継続の処理を行いたい場合に必要です。underの後に続く継続の処理を行いたくない場合は「return」と書いて、未定義値を返却してください。

以下のようにunderを変更すると、継続の処理が行わずに画面に「Access deny」と表示されるのが確認できます。

under sub {
  my $self = shift;
  
  my $continue = 0;
  
  # 継続処理を行わない場合
  unless ($continue) {
    $self->render(text => 'Access deny');
    return;
  }

  return 1;
};

ルーティングのサンプル

ルーティングを理解するためのサンプルコードを書いてみます。

use Mojolicious::Lite;

# 前処理
under sub {
  my $self = shift;
  
  # ユーザーがadminの場合は許可しない
  my $user = $self->url_for->path->parts->[0] // '';
  if ($user eq 'admin') {
    
    $self->res->code(403);
    $self->render(text => 'Forbidden');
    return
  }
  
  return 1;
};

# /ユーザー名/プロジェクト名/ディレクトリ名
# あるいは /ユーザー名/プロジェクト名
get '/:user/:project/*dir' => {dir => undef} => sub {
  my $self = shift;
  
  # パラメーター
  my $user = $self->param('user');
  my $project = $self->param('project');
  my $dir = $self->param('dir') // 'Nothing';
  
  # 描画
  $self->render(
    'index',
    user => $user,
    project => $project,
    dir => $dir
  );
};

any '/http-method' => sub {
  my $self = shift;

  # HTTPメソッドの取得
  my $http_method = $self->req->method;

  $self->render(text => "HTTP Method: $http_method");
};

app->start;

__DATA__

@@ index.html.ep
<%
  my $user = stash('user');
  my $project = stash('project');
  my $dir = stash('dir');
%>
<html>
  <head>
    <title>Routing</title>
  </head>
  <body>
    User: <%= $user %><br>
    Project: <%= $project %><br>
    Directory: <%= $dir %></br>
  </body>
</html>

アプリケーションの解説

このアプリケーションは次のURLでアクセスできるようになっています。

/ユーザー名/プロジェクト名/ディレクトリ名
/ユーザー名/プロジェクト名

「/kimoto/dog/dir1/readme.txt」でアクセスすれば、次のように表示されます。

User: kimoto
Project: dog
Directory: dir1/readme.txt

「/kimoto/dog」でアクセスすれば、次のように表示されます。

User: kimoto
Project: dog
Directory: Nothing

これは次のようにルーティングを定義しているからです。

get '/:user/:project/*dir' => {dir => undef} => sub { ... };

また「/admin/dog/dir1/readme.txt」でアクセスすれば、「Forbidden」と表示されます。これは以下の前処理を記述しているためです。

# 前処理
under sub {
  my $self = shift;
  
  # ユーザーがadminの場合は許可しない
  my $user = $self->url_for->path->parts->[0] // '';
  if ($user eq 'admin') {
    
    $self->res->code(403);
    $self->render(text => 'Forbidden');
    return
  }
  
  return 1;
};

URLのパスの最初の部分が「admin」の場合は、ステータスコードを「403」に設定して「Forbidden」というテキストを返却しているようにしています。

少し解説します。

# URLのパスの最初の部分を取得
my $user = $self->url_for->path->parts->[0] // '';

まずアクセスしたURLを取得するにはurl_forメソッドを使用します。取得するのはMojo::URLオブジェクトです。次のこのURLのパスの部分を取得するには、Mojo::URLクラスのpathメソッドを使用します。取得するのはMojo::Pathオブジェクトです。次にパスの各部分は配列として保存されていて、これはMojo::Pathクラスのpartsメソッドによって取得できます。

「//」は、左辺が未定義値だった場合に、右辺を代入できるperlの「defined-or演算子」です。

このようにしてURLのパスの最初の部分を取得しています。

次にユーザーにチェックの部分です。

# ユーザーがadminであれば、ステータスコードに403を設定して、Forbiddenを表示
if ($user eq 'admin') {
  
  $self->res->code(403);
  $self->render(text => 'Forbidden');
  return
}

コントローラークラスのresメソッドを使用すればレスポンスオブジェクトを取得できます。これはMojo::Message::Responseオブジェクトです。Mojo::Message::Responseクラスのcodeメソッドを使用すれば、ステータスコードを設定できます。

次の部分ではHTTPメソッド名を取得しています。

# HTTPメソッドの取得
my $http_method = $self->req->method;

「/http-method」にアクセスすると「HTTP Method: GET」と表示されます。

関連情報