取势 明道 优术

作者为 扶 凯 发表

完成了挖掘更多的中间件的使用方法后, 我们也来学习自己怎么写中间件.

写中间件
我们开发的 PSGI 的中间件,我们要让它使用起来就象标准的 PSGI 的应用, 中间件会取得源 PSGI 应用内容来放到自己的功能上调用. 所以从服务器上看起来像应用程序, 但从用户上来看它就像服务器.
一个简单的中间件伪造 HTTP 的 user-agent 的:

# Wrapped application
my $app = sub {
    my $env = shift;
    my $who = $env->{HTTP_USER_AGENT} =~ /Mobile Safari/ ? 'iPhone' : 'non-iPhone';
    return [ 200, ['Content-Type','text/html'], ["Hello $who"] ];
};

# Middleware to wrap $app
my $mw = sub {
    my $env = shift;
    $env->{HTTP_USER_AGENT} .= " (Mobile Safari)";
    $app->($env);
};

如果有 iPhone 的浏览器(Mobile Safari),这个应用会只会显示 "Hello iPhone".但中间件增加了一句操作所有的传入请求中的 user-agent,所以如果你运行应用,打开后,你只能见到永远显示 "Hello iPhone".默认的日志中会打印:

127.0.0.1 - - [23/Dec/2009 12:34:31] "GET / HTTP/1.1" 200 12 "-" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-us) AppleWebKit/531.21.8 (KHTML, like
Gecko) Version/4.0.4 Safari/531.21.10 (Mobile Safari)"

你可以见到 (Mobile Safari) 的字段被加入进了 User-Agent 的字符中.


写一个可重用的中间件
上面这给了一个很好的例子, 给自己写的中间件先放到 .psgi 文件中,但如果是你经常使用,需要它足够通用才能在其它应用程序中重用. 这时你应该写成下面这样, 先直接 parent Plack::Middleware 继承方法来使用.

package Plack::Middleware::FakeUserAgent;
use strict;
use parent qw(Plack::Middleware);
use Plack::Util::Accessors qw(agent);

sub call {
    my($self, $env) = @_;
    $env->{HTTP_USER_AGENT} = $self->agent;
    $self->app->($env);
};

1;

 这就是了.你这时需要做的是继承 Plack::Middleware 和定义你的中间件相关的选项与方法的调用, 后面会将 $self->app 来取得原来应用程序的内容.现在这个中间件兼容 Plack::Builder DSL 风格,所以你可以直接使用:

use Plack::Builder;

builder {
    enable "FakeUserAgent", agent => "Mozilla/3.0 (MSIE 4.0)";
    $app;
};

这个伪造是进来的请求.象 Internet Explorer.你也可以使用 enable_if 的条件来控制是否启动这个中间件

Post process requests(对操作进行后处理)
前面的例子会在请求进来前来调用,操作请求的 $env 的 hash 传进来 .但如何处理 response 所回应的响应啦?这的代码几乎和前面是相同的:

my $app = sub { ... };

# Middleware to fake status code to 500
my $mw = sub {
    my $env = shift;
    my $res = $app->($env);
    $res->[0] = 500 unless $res->[2] == 200;
    $res;
};

这是一个比较邪恶的中间件,呵呵。这所实现的意思是讲只要响应不是 200 ,它就会改变全部的 http 的状态码为 500. 我也不知道是否真的有任何的实际作用.这个和上面的处理不同的是调用 $app 的时机,这个是提前调用应用,然后自己在修改应用的内容.
由于一些特殊的流媒体接口的服务器, 需要实现延迟 HTTP 响应, 这在个别中间件中, 这样的回调会让处理效率不高, 所以我们在 Plack::Middleware 中有一个特殊的回调接口, 使的这个功能变得更加简单:

package Plack::Middleware::BadStatusCode;
use strict;
use parent qw(Plack::Middleware);

sub call {
    my($self, $env) = @_;
    my $res = $self->app->($env);
    $self->response_cb($res, sub {
        my $res = shift;
        $res->[0] = 500 unless $res->[0] == 200;
    });
}

1;

通过 response 变量中的 $res 需要使用 response_cb 设置回调来现实回应, 该方法支持直接响应和延迟响应. 常用于事件处理。

名字空间
在这个例子中,我们使用了 Plack::Middleware 的名字空间来写中间件,但我们不一定真的都想用这个,如果您认为您的中间件是通用的,所有 PSGI 都能从你的应用中受益,我们使用默认的名字空间,但如果中间件是自己为一些事情具体定制,或定制为特定的应用程序框架,然后想使用任何命名空间,如:

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

使用 + (plus) 的符号,指出你使用全名的名字空间

enable '+MyFramework::Middleware::Foo', ...;

我们也可以不使用 DSL 风格.

$app = MyFramework::Middleware::Foo->wrap($app, ...);

现在你的应用能正常的工作了..

 这是我的译文,原地址:http://advent.plackperl.org/2009/12/day-23-write-your-own-middleware.html

 

 

 

来了就留个评论吧! 1个评论