mod_perl编程介绍
2014-11-27 17:10:38 阿炯

本站赞助商链接,请多关照。 mod_perl是个Apache模块,它巧妙的将Perl程序语言封装在Apache web服务器内。

1. 介绍性指南
mod_perl 是个庞大而复杂的工具,它内建了许多模块帮助你方便地构建动态网站。这篇指南的目的是帮助你构建一个良好的 mod_perl 模块,并从中理解 mod_perl 的实现技术。我并不推荐使用这里介绍的技术来建立一个大型站点,尤其对于一个刚刚涉足 mod_perl 的人来说。但我推荐大家可以深入看一下它的一些内建的方案,比如 mason, axkit, embperl, apache::asp 和 pagekit 等等。

1.1. 你需要什么?
本指南假设你已经有过安装和测试 mod_perl 的经验,以及较新版本的 apache 的安装经验。因为有可能你需要在你的机器上实现时适当的修改本文提供的配置,我们需要你安装一些模块并且需要进入 apache 的配置目录作修改。所以最好你有 root 权限来做这些事情,当然你还需要一个文本编辑器。

1.2. 切入正题
mod_perl 模块也是 perl 模块,但它有较为特别的设计。最方便的创建一个 perl 模块的方法就是使用标准的 perl 分发自带的工具 h2xs ,你可以在命令行模式键入 h2xs 来看看它的参数列表。现在,到一个适当的目录开始一个新项目,键入:
h2xs -a -x -n apache::tutorial::first
h2xs 将会创建目录 apache, 以及其他一些子目录.现在进入最深一级的目录看看:
cd apache/tutorial/first

在这个新目录里面,你可以看到 5 个文件: changes, first.pm, manifest, makefile.pl 和 test.pl。它们的作用如下:
changes
这个文件作为你的项目的修改日志(changelog)文件。

first.pm
这是主模块文件,包含你的 mod_perl 句柄代码(handler code)。

manifest
本文件用于自动构建 tar.gz 类型的模块版本分发。这样你就可以把你的模块拿到 cpan 发布或者分发给其他人。它包含了你在这个项目中所有文件的列表。

makefile.pl
这是标准的 perl makefile 构造器。用于创建 makefile.pl 文件来编译该模块。

test.pl
针对该模块的一些测试脚本。默认情况下它只是检查模块的编译和载入

好了,现在我们开始把 first.pm 变为可工作的 mod_perl 模块。使用文本编辑器打开该文件,修改后的内容如下:
package apache::tutorial::first;
use strict;use vars qw/$version/;
use apache::constants;
$version = 0.01;
sub handler {
my $r = shift;
$r->send_http_header('text/html');
print "<html><body>hello world</body></html>";
return ok;
}
1;

不要忘记文件末尾的"1;",对于 perl 来说,一个模块最后返回的非零值表示该模块已经被成功编译。

1.3. 安装你的模块
h2xs 工具使我们的模块安装工作极为方便。在和你的 first.pm 文件相同的目录中。键入:
perl makefile.plmakemake test
如果 make test 成功的话,你需要以 root 身份执行:
make install
这样你就把你的模块安装到了 perl 的库目录(library directory)。

1.4. 添加该模块为 apache 的一个句柄(handler)
现在我们需要进入 apache 配置目录来修改配置文件,使我们的模块作为 apache 内容处理阶段的处理器。打开 httpd.conf 文件,在末尾加入如下配置:
<location /mod_perl_tutorial>
    sethandler perl-script perlhandler apache::tutorial::first
</location>
然后保存配置文件,并且重新启动 apache 服务器:
apachectl stopapachectl start
现在使用浏览器访问 http://localhost/mod_perl_tutorial ,你将如期的看到显示 "hello world" 页面。

2. 这里都发生了些什么?
好,在这里都发生了什么事情呢?
当 apache 启动的时候,它读取它的配置指令并把适当的命令传递给相应的处理该命令的模块。这里有两个相关的指令 sethandler 和 perlhandler。
第一个指令 sethandler 由 mod_mime 模块处理,该指令表示使用什么模块作为处理请求的主要部分。这里所设置的 perl-script 表示使用 mod_perl 来处理请求。

第二个指令 perlhandler 由 mod_perl 模块来处理,它只是简单的说明使用我们的模块来处理请求的主要部分。有一点需要注意,无论何时在你有一个 perlhandler 时,你需要相应的 sethandler perl-script 配置指令。这样才能使你的 mod_perl 代码起作用。我总是认为这是一个弱点,但这将涉及 apache 内部的处理机制,所以在将来这也很难改变。

现在请求来了,apache 查看用什么模块来处理相应的 uri 并且在这里决定使用 mod_perl,而 mod_perl 知道它必须把请求发送给我们的模块,并调用我们模块的 handler() 函数作为 apache::request 对象的第一个参数。而我们的 handler() 函数的返回值决定了下一步 apache 将要做什么。现在我们知道返回值 ok 意味着一切成功。 ok 是个从 apache::constants 模块导出的常量。

3. 调试
如果你没有看到 "hello world", 那你可能看到了一个错误页面,或者其他什么完全不同的。第一步去查看错误日志看看到底是什么发生了错误。我习惯于在浏览器中请求后立即查看错误日志。你可以使用 tail 工具:
tail -f /path/to/apache/logs/error_log
(使用你的真实 error_log 路径替换上面的路径。如果你不肯定它在哪里,查看你的 httpd.conf 文件的 errorlog 指令部分)
现在重新载入页面,然后 error_log 将告诉你什么地方出现了问题。更多的关于 perl 调试,请参见 perldebug.

4. 加入更多
现在如果你想要针对上面的情况作一些修改,该如何做呢?不幸的,唯一一种安装模式如下:
修改你的 first.pm 文件
重新以 root 身份运行 make install
重新启动 apache
这也许很麻烦,特别是重新启动 apache。针对这个问题,我们可以另外安装一个特别设计的模块来避免每次这样麻烦的做。首先你需要从 cpan 下载并安装 apache::reload 模块(除非你已经使用 mod_perl 1.26 或者更高版本)。在这里 http://search.cpan.org/search?dist=apache-reload 下载。

解开 tar.gz 文件并进入新目录,执行:
perl makefile.plmake
然后到 root 身份执行:
make install
现在再次打开 httpd.conf 文件,加入:
perlinithandler apache::reload

这将测试所有有所改变的模块并在必要时自动重新载入新模块。这对于开发来说很有用,但会有性能损失,所以在开发完成之后,就将该特性关闭。

5. 阅读更多
从这里开始你有很多事情需要去做。apache api 本身就十分庞大,大多数都可以通过 perldoc apache 看到相应的文档. 现在这个模块基本上没有什么价值,因为只有一个 uri 可以用于被该模块所控制(http://server/mod_perl_tutorial),这使得它变得不够灵活。为了使一个模块可以处理多个uri ,有许多解决办法,但最好的还是推荐使用 apache::dispatch模块。允许你保留标准的 mod_perl handler 构架,同时还允许多个函数和多个uris被派发。

接下来我不建议象例子中一样直接向浏览器输出内容。请考虑使用一些常用的模版技术,比如 template-toolkit, html::template, 更甚于使用 xslt 或者 xpathscript (有很多很多这样的模版技术可选,我们希望有一天可以有文章来讨论这些技术来帮助你来选择)。

Apache 和 IBM HTTP Server 的最终配置
确保 /path/to/apache/conf/httpd.conf 包含 "LoadModule perl_module libexec/libperl.so" 和 "AddModule mod_perl.c" 语句(如果要求),以及其它您需要的所有伪指令。例如:
 <Location /perl>
  SetHandler perl-script
  PerlHandler Apache::Registry
  Options ExecCGI
  allow from all
  PerlSendHeader On
 </Location>

 <Location /perl-status>
  SetHandler per-script
  PerlHandler Apache::Status
  order deny,allow
  deny from all
  allow from X.X.X.X  #replace X.X.X.X with a useful IP address.
 </Location>

在Win32平台下配置mod_perl。修改httpd.conf,增加以下几行:
LoadFile "C:/Perl/bin/perl58.dll"
LoadModule perl_module modules/mod_perl.so
<Files *.cgi>
 SetHeader perl-script
 PerlHandler ModPerl::PerlRun
 Options +ExecCGI
</Files>

Configuration

Once you have mod_perl installed you will need to configure it in your httpd.conf by adding the following configuration options:
LoadModule perl_module modules/mod_perl.so
PerlRequire /path/to/perl/libs/startup.pl

The startup.pl script allows you to setup your @INC library path and preload any modules that you want shared among your Apache server children. For the examples that follow I'm using the following minimal startup.pl:
use lib qw(/path/to/perl/libs);
use Apache2;
1;

<Location /modperl/>
 SetHandler perl-script
 PerlResponseHandler Modperl::Registry
 PerlOptions +ParseHeaders
 Options +ExecCGI
</Location>

mod_perl 有三个层次:
* 第一种是就是用 ModPerl::Registry 或 ModPerl::PerlRun 来运行普通的 Perl 代码。
* 第二种是类如 Apache2::Hello 这样的模块。Catalyst, TT, Mason 等都提供的这种方式。
* 最后一种要涉及到跟 Apache 的内部,即一系列 Perl*Handler 打交道。
 
可能有一些不对,不过即使不中也不远也。

As follows are what I have done:
* subscribe to the maillist again(it's the fourth time, :> send a blank email to modperl-subscribe@perl.apache.org).
* download the free book - Practical mod_perl(Under CC license, tarball here)
* download some chapters of mod_perl Developer's Cookbook, it's FREE.
* read several articles on perl.com

I'd like to write some journal through my learning course.Writing HERE will beat me to read over it and NOT give up halfway.my plan is to compose more than 1 blog per week.

mod_perl 配置的一些指令
第一句见的最多的可能是 PerlModule

PerlModule
PerlModule Foo::Bar 就等同于 Perl 的 require Foo::Bar;
有点不同的可能是可以接受多个参数:PerlModule Apache::DBI CGI DBD::Mysql如果你需要传递 qw/foo bar/ 给 Foo::Bar 的话,一般将它写入一个 pl 文件里,然后用 PerlRequire 载入它。如 startup.pl:

use Apache2::Const -compile => ':common';
use APR::Const -compile => ':common';
...

PerlRequire
在 1.0 中在这是常用句,用于载入一个 Perl 文件。由于它不能控制代码的运行时间,所以在 2.0 中被 PerlConfigRequire 和 PerlPostConfigRequire 所细分。
PerlConfigRequire 是碰到该语句立即运行该参数文件,而 PerlPostConfigRequire 是在服务器运行的最后阶段才运行。 一般而言,PerlPostConfigRequire 才是我们所要的。
PerlRequire "C:/Apache2/conf/startup.pl"

PerlOption
提供 mod_perl 的编译选项。
如果在运行时需要确定某些选项是否已经启用,可使用 $r->is_perl_option_enabled($option) 或 $s->is_perl_option_enabled($option) 来判断。 一般允许用 +, 禁止用 - Enable

一般用于使某虚拟主机能用,而某主机不能使用 mod_perl. 如
<VirtualHost ...>
 PerlOptions -Enable
</VirtualHost>

Perl*Handler
可以设置哪些 Perl*Handler 我们能使用而哪些不能使用。如
<VirtualHost ...>
 PerlOptions -Authen -Authz -Access -Sections
</VirtualHost>

就禁止使用 PerlAuthenHandler, PerlAuthzHandler, PerlAccessHandler, 和 Sections/段。而
<VirtualHost ...>
 PerlOptions None +Response
</VirtualHost>

就只能使用 PerlResponseHandler

AutoLoad
大致是自动加载的意思。比如开启 PerlOptions +Autoload 后,当使用 PerlResponseHandler Apache::Magick 时前面就不需要先明显的加载 PerlModule Apache::Magick 模块。而是碰到后会自动加载。

ParseHeaders
它与 mod_perl 1.0 中的 PerlSendHeader On 功能一样。如果你在代码中使用 print "Content-type: text/html\n\n"; 的话将这个 PerlOption 开起来。它会将这句话自动转为调用 send_http_header ,这一般在 ModPerl::Registry 中用得比较多,因为 ModPerl::Registry 使用的旧代码都是这么输出的。

<Location /perl>
  SetHandler perl-script
  PerlResponseHandler ModPerl::Registry
  Options +ExecCGI
  PerlOptions +ParseHeaders
  PerlOptions +SetupEnv
</Location>

还有一些其他比较复杂的涉及到编译池或环境变量什么的。如果以后有机会用到再介绍。

PerlSwitches
Switch 的意思是开关。如我们在命令行时会使用 perl -e 这里的 -e 就是一个 switch。一般而言它最大的用途就是修改 @INC, (我们也可以在 startup.pl 里修改,然后 PerlRequire)用 PerlSwitches 的好处就是黏合力更强一些。如:
PerlSwitches -I/var/www/MyApp/lib

它的作用还类似于这么写:
<Perl>
 use lib qw(/var/www/MyApp/lib);
</Perl>
 
这三种办法都是可以的。
SetHandler
有两种类型,一种是 modperl, 另一种是 perl-script

perl-script
这是更为常见的类型的。它的默认配置为:
  * 如果没有 PerlOptions -GlobalRequest 的话,PerlOptions +GlobalRequest 将在 PerlResponseHandler 阶段起作用。
  * 如果没有 PerlOptions -SetupEnv 的话,默认将是 PerlOptions +SetupEnv
  * 将 STDIN 和 STDOUT 与 $r 绑定,这样就能使用 CORE::print() 来输出到 STDOUT 或从 STDIN 中读取。而 modperl 类型必须使用 $r->puts() 这样的函数来输出。
  * %ENV, @INC, $/, STDOUT 的 $| 和 END 块这种特殊变量将在调用 response handler 之前保存而在之后将恢复。
  * 任何加到 %ENV 的变量将传递给 subprocess_env 表,而后我们可以在接下来的 PerlLogHandler 和 PerlCleanupHandler 阶段中通过 r->subprocess_env 来获取。

modperl
这种类型下你只能调用 Perl*Handler 里的函数。如果你不需要以上 perl-script 的用途的话,使用 modperl 会带来更好的性能。
当配置成 PerlOptions +SetupEnv 时, modperl 类型只配置如下环境变量:$ENV{MOD_PERL}(总是存在) $ENV{PATH} 和 $ENV{TZ} (如果你在命令行/shell 或 httpd.conf 里设置了它们)。这时候你如果想传递一些配置变量的话一定要使用 PerlSetVar 和 PerlAddVar 而不是 PerlSetEnv 和 PerlPassEnv. 举一个例子:
 #httpd.conf
<Location /print_env2>
 SetHandler modperl
 PerlResponseHandler Apache2::VarTest
 PerlSetVar VarTest VarTestValue
</Location>

接下来在 Apache2::VarTest::handler() 里你可以通过
$r->dir_config('VarTest');

例子
file:MyApache2/PrintEnv1.pm
-----------------------
package Apache::PrintEnv1;
use strict;

use Apache::RequestRec ( ); # for $r->content_type
use Apache2::RequestIO (); # for print
use Apache2::Const -compile => ':common';

sub handler {
my $r = shift;
$r->content_type('text/plain');
for (sort keys %ENV){
print "$_ => $ENV{$_}\n";
}

return Apache2::Const::OK;
}
1;

这个需要配置成
PerlModule MyApache2::PrintEnv1
<Location /print_env1>
 SetHandler perl-script
 PerlResponseHandler MyApache2::PrintEnv1
</Location>

file:MyApache2/PrintEnv2.pm
------------------------
package MyApache2::PrintEnv2;
use strict;
use Apache2::RequestRec (); # for $r->content_type
use Apache2::RequestIO ();  # for $r->print
use Apache2::Const -compile => ':common';
 
sub handler {
  my $r = shift;
  $r->content_type('text/plain');
  $r->subprocess_env;
  for (sort keys %ENV){
  $r->print("$_ => $ENV{$_}\n");
  }
  return Apache2::Const::OK;
}
1;

这个可以配置成
PerlModule MyApache2::PrintEnv2
<Location /print_env2>
  SetHandler modperl
  PerlResponseHandler MyApache2::PrintEnv2
</Location>

不同点:
  * perl-script 可以用 print, 而 modperl 只能用 $r->print
  * perl-script 默认就用 PerlOptions +SetupEnv, 而 modperl 下一定要显性调用 $r->subprocess_env; 或开启 PerlOptions +SetupEnv

PerlResponseHandler
这个阶段是用于创建回复的。这毫无疑问是 mod_perl 中最最重要的阶段,而且这个指令是 mod_perl 中必须的两个指令之一。

<Location /hello>
  SetHandler perl-script
  PerlResponseHandler Apache2::Hello
</Location>

哪个指令都能少,就这两个是必须的。它指出了在 ResponseHandler 阶段所委托的模块。

参考链接
http://home.arcor.de/mailerstar/jeff/mod_perl/index.html