Perl Hackers Hub

第41回 Plack::Middleware再入門(1)

この記事を読むのに必要な時間:およそ 3 分

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはbayashiこと岡林大さんで,テーマは「Plack::Middleware再入門」です。

本稿のサンプルコードは,本誌サポートサイトから入手できます。

今も熱いPlack::Middleware

PSGIは「Perl Web Server Gateway Interface」の略で,Webサーバとアプリケーションをつなぐ仕様です。PlackはPSGI仕様に沿って実装されたモジュール群です。PSGI/Plackの登場によって,WebサーバとWebアプリケーションフレームワークのインタフェースが共通化されました。ChangesによるとPSGI/Plackのファーストリリースは2009年10月なので,丸7年が経過しています。Plack 1.0000からでも4年です。特殊な事情がない限り,近年Perlで実装されたWebアプリケーションはPlackに対応していると思います。

PSGI/Plackの中で,現在も新しいモジュールが登場しているクラスがあります。それは,Webサーバとアプリケーションをつなぐ層で活躍するPlack::Middlewareです。CPANには200を超えるPlack::Middlewareがアップロードされています。筆者は最近デバッグ用のPlack::Middlewareをよく書いていますが,マイクロサービス全盛の昨今,共通の認証処理などを実装している人も多いのではないでしょうか。

今回は,そのPlack::Middlewareに焦点を当てます。

Plack::Middlewareとは

Plack::Middlewareは,Webサーバとアプリケーションの間でアプリケーションに依存せずリクエストやレスポンスを処理します。ポイントはアプリケーションに依存しないという点で,たとえるならApacheやnginxのモジュールのような処理レイヤです。図1のようにアプリケーションをラップして処理を行います。

図1 Plack::Middlewareの概念図

図1 Plack::Middlewareの概念図

何も処理をしないシンプルなPlack::Middlewareの実装例は,次のようになります。

package Plack::Middleware::Foo;
use parent qw( Plack::Middleware );

sub call {
    my ($self, $env) = @_;

    # 1. ミドルウェアの処理

    my $res = $self->app->($env); # アプリケーションの処理

    # 2. ミドルウェアの処理

    return $res;
}

リクエストごとに呼ばれるcallメソッドで$envを受け取り,$res(PSGIレスポンス)を返します。$self->appがラップされるアプリケーションの処理で,その前後がミドルウェア本体の処理になります。

つまり,Plack::Middlewareには,

  • アプリケーションの処理の前に仕事をするもの
  • アプリケーションの処理のあとに仕事をするもの
  • アプリケーションの処理の前後両方で仕事をするもの

という3パターンがあります。

以降ではこの3パターンについて,具体的なPlack::Middlewareのモジュール実装を見ながら理解を深めます。

PSGIアプリケーションの入り口で処理

まずは,Plack::Middleware::ReverseProxyです。このミドルウェアはPSGIアプリケーションの入り口で処理をするPlack::Middlewareの代表例で,PSGIアプリケーションの前段にApacheやnginxなどのリバースプロキシがいる場合にREMOTE_ADDR(アクセス元IPアドレス)HTTP_HOST(ホストヘッダの値)⁠SERVER_PORT(ポート番号)などをオリジナルのものに書き戻してくれます。

ソースコードは次のようになっています注1)⁠

sub call {
    my $self = shift;
    my $env = shift;

    $env->{HTTPS} = $env->{'HTTP_X_FORWARDED_HTTPS'}
        if $env->{'HTTP_X_FORWARDED_HTTPS'};
    $env->{HTTPS} = 'ON'
        if $env->{'HTTP_X_FORWARDED_PROTO'}
        && $env->{'HTTP_X_FORWARDED_PROTO'} eq 'https';
    $env->{'psgi.url_scheme'} = 'https'
        if $env->{HTTPS} && uc $env->{HTTPS} eq 'ON';

    (省略)

    $self->app->($env);
}

Plack::Middleware::ReverseProxyによってアプリケーションではリバースプロキシによる環境変数の書き換えを意識せずにコーディングできます。もしこのミドルウェアがなければ,前段にいるリバースプロキシが書き換えた環境変数をアプリケーションで直接参照しなければならなくなり,ポータビリティが失われます。

このように,アプリケーションの前段で済ませておきたい処理はPlack::Middlewareが担う仕事の一つです。

注1)
本稿でのCPANモジュールのソースコードの紹介にあたっては,誌面の都合で筆者による改行や省略などの編集を行っています。

PSGIアプリケーションの出口で処理

続いては,アプリケーションの処理のあとに仕事をするPlack::Middleware::SimpleContentFilterです。このミドルウェアはレスポンスをフィルタできます。

次のコードでは,$self->appのあとにContent-Typeを確認しつつ,フィルタ処理を発行しています。

sub call {
    my $self = shift;

    my $res = $self->app->(@_);
    $self->response_cb($res, sub {
        my $res = shift;
        my $h = Plack::Util::headers($res->[1]);
        return unless $h->get('Content-Type');
        if ($h->get('Content-Type') =~ m!^text/!) {
            return sub {
                my $chunk = shift;
                return unless defined $chunk;
                local $_ = $chunk;
                $self->filter->();
                return $_;
            };
        }
    });
}

$self->response_cbPlack::Utilに属する関数が登場していますが,詳細は後述します。ここでは$self->appのあとに処理をしているという構造に注目しておきましょう。

PSGIアプリケーションの入り口と出口で処理

さらに,アプリケーションの入り口と出口の両方で処理を行うPlack::Middlewareも見てみましょう。アプリケーションの実行時間をHTTPヘッダにセットしてくれるPlack::Middleware::Runtimeです。

次のコードでは,$self->appの前に時間を取っておき,レスポンスの際に経過時間をHTTPヘッダに追加するという処理を行っています。

sub call {
    my($self, $env) = @_;

    my $start = [ Time::HiRes::gettimeofday ];
    my $res = $self->app->($env);
    my $header = $self->header_name || 'X-Runtime';

    $self->response_cb($res, sub {
        my $res = shift;
        my $req_time
            = sprintf '%.6f',
                Time::HiRes::tv_interval($start);
        Plack::Util::header_set(
            $res->[1],
            $header,
            $req_time
        );
    });
}

このような処理は,$self->appをラップしているという設計であればこそです。もしアプリケーションの前後に処理が分離した設計だったら,グローバル変数で時間をやりくりするようなことになり,あまりきれいとは言えなくなっていたでしょう。

<続きの(2)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.99

2017年6月24日発売
B5判/160ページ
定価(本体1,480円+税)
ISBN978-4-7741-8987-1

  • 特集1
    [Rubyで学ぶ!]良いコードって何だろう?
    現場で光る✨ 変数,メソッド,クラス,モジュール活用法
  • 特集2
    [iOS/Android両対応!!]UIテスト自動化
    Espresso,XCTest,Appium
  • 特集3
    実践Kubernetes
    Google Container Engineではじめる新時代のコンテナ管理!
  • 一般記事
    チーム内の対立解消
    事例で学ぶ原因分析,解決方法

著者プロフィール

岡林大(おかばやしだい)

主にPerlでサーバサイドを書く人ですが,たまにJavaScriptでフロントも書きます。自由でロックなWebが大好き。お仕事以外ではラーメンを食べるかOSSに貢献する#yapcramenの人。

GitHub:bayashi

コメント

コメントの記入