扶凯

取势 明道 优术

作者为 扶 凯 发表

使用 PSGI 来做 Web 应用开发方面最大的好处是,一旦你在你适配的框架上运行PSGI,你不用关心和了解其它的一切,原来需要你很处理的东西,例如,处理 FastCGI 和 CGI 在 Web 服务器之间的一堆配置.
同样,如果你有一个自己的大规模的 Web 应用,开源或者专有的,你可能是自己写的Web应用框架.
上次我们聊到怎么样转换存在的 web 应用框架适配到 PSGI 的接口.

基于 CGI.pm 的框架
上次我讲了怎么转换基于 CGI::Application 的应用来使用 PSGI ,只要使用 CGI::Application::PSGI 就行了.所以只要是使用 CGI.pm 的框架,只要使用CGI::PSGI 来代替就好了,只要定义一个类是最方便的方法.

package CGI::Application::PSGI;
use strict;
use CGI::PSGI;

sub run {
    my($class, $app) = @_;

    # HACK: deprecate HTTP header generation
    # -- CGI::Application should support some flag to turn this off cleanly
    my $body = do {
        no warnings 'redefine';
        local *CGI::Application::_send_headers = sub { '' };
        local $ENV{CGI_APP_RETURN_ONLY} = 1;
        $app->run;
    };

    my $q    = $app->query;
    my $type = $app->header_type;

    my @headers = $q->psgi_header($app->header_props);
    return [ @headers, [ $body ] ];
}

这是非常简单的,CGI::Application使用 run() 的方法,通常这个会返回全部的输出,包括 HTTP 的 header 和内容.所以必须修改 disable 的 header 不要输出在 body 中,需要使用 CGI::PSGI 中的 psgi_header 的方法来生成 status code 和 HTTP header 成一个 array 的引用.
所以我们为 Mason 和 maypole 实现的 PSGI 来适配看起来是一样的

  • 使用 $env 做参数来建一个 CGI::PSGI 的对象,来替换掉默认 CGI.pm 内的一些方法.
  • 如果有必要,需要 Disable 掉输出的 HTTP 的头
  • 调度运行的应用程序
  • 取出 HTTP 的头信息,然后 psgi_header 来生成 status 和 headers.
  • 到出内容放到 response 的 body 中

适配基本的框架
如果框架已经适配了方法来抽象这个服务器环境,它会很容易适配 PSGI,本着重复使用 CGI 适配的代码原则.下面的代码使用 Squatting 来适配 PSGI.这是使用模块为 Squatting::On::* 开头的来适配一些环境,象 mod_perl,FastCGI和一些其它的框架象 Catalyst 和 HTTP::Engine. 它是非常的容易通过自己写一个叫 Squatting::On::PSGI 的模块:

package Squatting::On::PSGI;
use strict;
use CGI::Cookie;
use Plack::Request;
use Squatting::H;

my %p;
$p{init_cc} = sub {
  my ($c, $env)  = @_;
  my $cc       = $c->clone;
  $cc->env     = $env;
  $cc->cookies = $p{c}->($env->{HTTP_COOKIE} || '');
  $cc->input   = $p{i}->($env);
  $cc->headers = { 'Content-Type' => 'text/html' };
  $cc->v       = { };
  $cc->status  = 200;
  $cc;
};

# \%input = i($env)  # Extract CGI parameters from an env object
$p{i} = sub {
  my $r = Plack::Request->new($_[0]);
  my $p = $r->params;
  +{%$p};
};

# \%cookies = $p{c}->($cookie_header)  # Parse Cookie header(s).
$p{c} = sub {
  +{ map { ref($_) ? $_->value : $_ } CGI::Cookie->parse($_[0]) };
};

sub psgi {
  my ($app, $env) = @_;

  $env->{PATH_INFO} ||= "/";
  $env->{REQUEST_PATH} ||= do {
      my $script_name = $env->{SCRIPT_NAME};
      $script_name =~ s{/$}{};
      $script_name . $env->{PATH_INFO};
  };
  $env->{REQUEST_URI} ||= do {
    ($env->{QUERY_STRING})
      ? "$env->{REQUEST_PATH}?$env->{QUERY_STRING}"
      : $env->{REQUEST_PATH};
  };

  my $res;
  eval {
      no strict 'refs';
      my ($c, $args) = &{ $app . "::D" }($env->{REQUEST_PATH});
      my $cc = $p{init_cc}->($c, $env);
      my $content = $app->service($cc, @$args);

      $res = [
          $cc->status,
          [ %{ $cc->{headers} } ],
          [ $content ],
      ];
  };

  if ($@) {
      $res = [ 500, [ 'Content-Type' => 'text/plain' ], [ "
$@
" ] ]; } return $res; }

这是非常直截了当地,特别当对照的Squatting::On::CGI. 他也许一行一行的 copy 和做一些调整.通过使用 Plack::Request 来解析这些参数来替换 CGI.pm 的.
同样地 Catalyst 使用 Catalyst::Engine 来抽象,和使用 Catalyst::Engine::PSGI 来适配在 PSGI 上运行 Catlayst.这其中大部分的代码是抄 CGI 的.

 

mod_perl centric frameworks
一些框架是基于 mod_perl 的 API 来开发的.在这种情况下,我们已经看到不能采取类似的方法了.相反,你可能开始嘲弄 Apache::Request 的 API 使用了 fake/mock 的对象.Patric Donelan ,一个 WebGUI 的开发者解释了他的做法,他的 blog 有相关的模拟mod_perl 的 API 请求过来的类 .这也种方法也许是一种好的开始.

这是我的译文,原地址:http://advent.plackperl.org/2009/12/day-8-adapting-web-frameworks-to-psgi.html

来了就留个评论吧! 没有评论