mod_perl

mod_perl 的主要作用在于将 Perl 的解释器嵌入到 Apache 中.

感觉比较重要的几个模块:

  • CGI
  • Apache::Registry
  • Apache::PerlRun

学习 CGI 脚本的编写只需要处理好两件事:

  • how to accept data
  • how to generate output

主要就是接收 header 内容, 做修改后将新的 header 发送给服务器软件处理.

一个最简单的 CGI 脚本:

1
2
3
4
#!/usr/bin/perl -Tw

print "Content-type: text/plain\n\n";
print "Hello world!\n";

一般在 Web Server 的配置目录下都有 mime.types 文件, 其包含 a list of MIME types.

注意: 根据 HTTP 协议要求, header 之后必须要有一个空行 (也就是这里为什么是 \n\n), 其表明响应信息的开始.

在 Apache 中:

1
http://localhost/cgi-bin/hello.pl?username=Doug

中的 username=Doug 被称为 QUERY_STRING

mod_cgi

运行在 mod_cgi 之下, GATEWAY_INTERFACE 环境变量的值为 CGI/1.1, 而运行在 mod_perl 之下, 其值为 CGI-Perl/1.1

REMOTE_ADDR 显示服务器的 IP.

REQUEST_METHOD 显示访问的方式.

QUERY_STRING 显示请求的参数.

HTTP_USER_AGENT 显示访问者的 user agent.

注意 QUERY_STRING 的长度有限制, 在 Apache 中为 8KB. 更多的数据通过 POST 提交.

mod_perl 相对于 mod_cgi 的优势

mod_cgi 中运行 Perl 程序时, 每一次请求都会加载和结束一次 Perl 解析器 (都会重新 fork), 且所有变量都会重新声明和使用.

在使用 mod_perl 时, Perl 解析器在一个 child process 中只加载一次, 在这个 child process 处理完一个 Perl 脚本之后, 其不会 exit. 因此代码也只被载入和编译一次.

mod_perl 也包含 Apache core 的 API, 因此可以用 Perl 编写 Apache 的完整模块. 也可以完全用 Perl 配置 Apache.

在加载 mod_perl 之后, 可以用一些 mod_perl-specific configuration directives 来配置, 其大多以 Perl 开头, 如:

1
2
3
4
5
# 指定加载 Perl 模块
PerlModule MyModule

# 指定 Apache 启动时要加载的 Perl 脚本
PerlRequire /path/to/startup.pl

等.

缺点为, 让 httpd 程序占用更多的内存.

.htaccess 文件

.htaccess 是 Hypertext Access 的缩写。是 Apache 的配置文件.

但其通常位于网站的根目录或特定目录中. 可以在不修改全局服务器配置文件而定制网站的行为.

但是如果在 httpd.conf 文件中编写了:

1
2
3
<Directory />
AllowOverride None
</Directory>

.htaccess 不会起作用, 除非某一个具体的目录设置 AllowOverrideNone 以外的值.

也可以用 AccessFileNameApache 寻找其他文件:

1
AccessFileName .acl

此时会找 .acl 而不是 .htaccess

对于 .ht* 文件, Apache 会设置让其不被 Web 访问, 而自定义时, 需要添加:

1
2
3
4
<Files .acl>
Order Allow,Deny
Deny from all
</Files>

mod_perl 运行 CGI 脚本

主要用:

  • Apache::PerlRun
  • Apache::Registry

这两个模块.

注意两者的特点和区别:

Apache::Registry, 只编译脚本一次, 所有全局变量值在多个 requsts 之间会保留.

Apache::PerlRun, 每一个 requsts 都会编译脚本一次.

Apache::Registry 会检查脚本的 last-modification time, 如果文件被修改, 则重新编译.

Apache 的 module registry

Apache 会把一次 request 的处理分为多个 phases, 便于对某一个 phase 做 hook 处理.

用于处理特定 phase 的 subroutine 或 function 都被称为 handler.

如处理 authentication 的 handler 有 mod_auth_dbi, 处理内容的 handler 有 mod_cgi

mod_perl 和其他模块一样, 都是用 C 编写的. 但是其提供了用 Perl 编写 Apache 模块的 API.

对于如何切换 Apache::PerlRunApache::Registry, 在 Apache 配置文件中添加如:

1
2
3
4
5
6
7
8
9
Alias /perl/ /home/stas/modperl/
PerlModule Apache::PerlRun
<Location /perl/>
SetHandler perl-script
PerlHandler Apache::PerlRun
Options ExecCGI
PerlSendHeader On
Allow from all
</Location>

一个简单的 mod_perl Content Handler

如, 编写一个 ModPerl::Rules1 的模块:

1
2
3
4
5
6
7
8
9
package ModPerl::Rules1;
use Apache::Constants qw(:common);

sub handler {
print "Content-type: text/plain\n\n";
print "mod_perl rules!\n";
return OK; # We must return a status to mod_perl
}
1; # This is a perl module so we must return true to perl

将其放在 @INC 的路径中.

另一个 ModPerl::Rules2 模块:

1
2
3
4
5
6
7
8
9
10
package ModPerl::Rules2;
use Apache::Constants qw(:common);

sub handler {
my $r = shift;
$r->send_http_header('text/plain');
$r->print("mod_perl rules!\n");
return OK; # We must return a status to mod_perl
}
1; # This is a perl module so we must return true to perl

/etc/httpd/conf/httpd.conf 文件中添加:

1
2
3
4
5
6
7
8
9
10
11
12
PerlModule ModPerl::Rules1
<Location /mod_perl_rules1>
SetHandler perl-script
PerlHandler ModPerl::Rules1
PerlSendHeader On
</Location>

PerlModule ModPerl::Rules2
<Location /mod_perl_rules2>
SetHandler perl-script
PerlHandler ModPerl::Rules2
</Location>

然后就能直接访问到 CGI 脚本.

从这里可以得到几点:

  • PerlHandler, 指定将 requsts 传送给哪一个 Perl 模块处理, 其会自动调用相应模块中的 handler 函数
  • PerlSendHeader On, 让 Apache 在请求处理过程中自动发送适当的 HTTP 头部,例如 Content-Type 和 Content-Length

多种 mod_perl 安装

先获取 Apache 和 mod_perl 的源代码:

1
2
wget https://dlcdn.apache.org/httpd/httpd-2.4.57.tar.gz
wget https://dlcdn.apache.org/perl/mod_perl-2.0.12.tar.gz

注意这里的版本. 且这里用的是 mod_perl2

解压之后, 主要依靠 perl Makefile.PL 进行配置.

perl Makefile.PL 的选项

具体还是看书吧.

启用 callbacks hooks

可选的有:

注意除了 PerlHandler, PerlChildInitHandler, PerlChildExitHandler, PerlConnectionApi, PerlServerApi 这些是默认可用的, 其余需要在 perl Makefile.PL 时添加参数来配置.

开启所有 callback hooks, 添加:

1
ALL_HOOKS=1

或者:

1
EVERYTHING=1

启用标准的 API 特性

有:

  • PERL_FILE_API=1
  • PERL_TABLE_API=1
  • PERL_LOG_API=1
  • PERL_URI_API=1
  • PERL_UTIL_API=1
  • PERL_CONNECTION_API=1
  • PERL_SERVER_API=1

都是启用 Perl 模块的使用, 如 Apache::File.

这些选项都是默认关闭的, 可以用 EVERYTHING=1 或者 DYNAMIC=1 全部启用.

启用额外的特性

  1. <Perl> section 在 httpd.conf 文件中用 Perl 语言直接配置 Apache
1
perl Makefile.PL PERL_SECTION=1
  1. SSI 技术支持
1
perl Makefile.PL PERL_SSI=1
  1. 启用 Apache::ModuleConfig, 和 Apache::CmdParms
1
perl Makefile.PL PERL_DIRECTIVE_HANDLERS=1
  1. stacked handlers
1
perl Makefile.PL PERL_STACKED_HANDLERS=1
  1. method handlers
1
perl Makefile.PL PERL_METHOD_HANDLERS=1

复用配置参数

将配置选项写入 makepl_args.mod_perl (还有其他形式) 文件中, Makefile.PL 会自动读取.

Apache 和 mod_perl 配置

Apache 配置

若不想 .htaccess 默认起作用, 可以在 httpd.conf 文件中加入:

1
2
3
<Directory />
AllowOverride None
</Directory>

此时若还想 .htaccess 文件起作用, 需要在特定目录的配置中加入 AllowOverrideNone 以外的值.

为了防止外部访问, 一般需要加上:

1
2
3
4
<Files .htaccess>
Order Allow,Deny
Deny from all
</Files>

更改端口

1
Port 8080

修改子进程的用户和组

1
2
User httpd
Group httpd

<Directory>, <Location>, 和 <Files> Sections

<Directory>

一般 <Directory> 作用于目录. 用绝对路径, 相对于 server 的根目录:

1
2
<Directory /usr/local/apache/htdocs/pub>
</Directory>

<Files>

<Files filename>...</Files> 可以写在 server 和 virtual host 的配置中, 以及 .htaccess 文件.

<Files> 作用于一个或多个文件.

<Files> section 可以嵌套在 <Directory> 中, 但不能嵌套在 <Location> 中.

可以通过 ~ 启用扩展正则:

1
2
3
4
5
<Files ~ "\.(pl|cgi)$">
SetHandler perl-script
PerlHandler Apache::Registry
Options +ExecCGI
</Files>

嵌套的示例如:

1
2
3
4
5
<Directory /home/httpd/docs>
<FilesMatch "\.(html|txt)$">
PerlHandler +Apache::Compress
</FilesMatch>
</Directory>

这里 PerlHandler 之后的 + 表明让 mod_perl 加载 Apache::Compress 模块.

<Location>

<Location URI>...</Location> 可以写在 server 和 virtual host 的配置文件中.

<Location> 作用于 URI. 相对路径, 相对于设置的一些根目录:

1
2
<Location /pub>
</Location>

作用于不存在于文件系统的路径如:

1
2
3
4
5
6
<Location /status>
SetHandler server-status
Order Deny,Allow
Deny from all
Allow from .example.com
</Location>

处理顺序

  1. <Directory>.htaccess
  2. <DirectoryMatch><Directory ~ >
  3. <Files><FilesMatch>
  4. <Location><LocationMatch>

<Directory> 先处理短路径, 后处理长路径, 如先 /, 后 /home/www.

mod_perl 配置

最好是把 mod_perl 的配置放在 Apache 配置文件的最后.

或者是外部引用, 如:

1
2
3
<IfModule mod_perl.c>
Include conf/mod_perl.conf
</IfModule>

mod_perl 引入了几个新的 directives:

  • <Perl>
  • PerlModule
  • PerlRequire

<Perl>

其允许在 Apache 配置文件中嵌入 Perl 代码. 如:

1
2
3
4
5
6
<Perl>
# 在 Apache 启动时执行的 Perl 代码
use strict;
use warnings;
$ENV{MY_VARIABLE} = "Hello, World!";
</Perl>

PerlModule

等价于 Perl 中的 use.

同样用于加载 Perl 模块:

1
PerlModule My::Module1 My::Module2

可以一次加载多个.

PerlRequire

等价于 Perl 中的 require.

用于加载一个 Perl 模块或脚本, 如:

1
PerlRequire /home/httpd/perl/lib/startup.pl

注意这里的 startup.pl 的最后一行写成 1;

Alias 配置

Web 中大多访问的路径都不是物理存在的, 而是通过映射到物理路径上.

在 Apache 中可以用 ScriptAliasAlias 进行映射, 如:

1
Alias /foo /home/httpd/foo

这里将 /foo 的请求映射到 /home/httpd/foo 目录. 如一个请求为 http://www.example.com/foo/test.pl, 则会被处理为 /http://www.example.com/home/httpd/foo/test.pl

ScriptAlias 不仅会进行映射操作, 还会运行匹配的文件:

1
ScriptAlias /cgi-bin /home/httpd/cgi-bin

其相当于:

1
2
3
4
5
Alias /cgi-bin /home/httpd/cgi-bin
<Location /cgi-bin>
SetHandler cgi-script
Options +ExecCGI
</Location>

由于 SetHandler 调用 mod_cgi 模块, 因此若想使用 mod_perl, 最好还是用 Alias, 如:

1
2
3
4
5
6
Alias /perl/ /home/httpd/perl/
<Location /perl>
SetHandler perl-script
PerlHandler Apache::Registry
Options +ExecCGI
</Location>

<Location /perl> Sections

如:

1
2
3
4
5
6
7
8
9
Alias /perl/ /home/httpd/perl/
PerlModule Apache::Registry
<Location /perl>
SetHandler perl-script
PerlHandler Apache::Registry
Options +ExecCGI
Allow from all
PerlSendHeader On
</Location>

Perl*Handlers

Apache 的 request loop 有 11 个 phases:

  • Post-read-request
  • URI translation
  • header parsing
  • access control
  • authentication
  • authorization
  • MIME type checking
  • fixup
  • response
  • logging
  • cleanup

mod_perl 为每一个 phases 提供有 Handler:

  • PerlPostReadRequestHandler
  • PerlInitHandler
  • PerlTransHandler
  • PerlHeaderParseHandler
  • PerlAccessHandler
  • PerlAuthenHandler
  • PerlAuthzHandler
  • PerlTypeHandler
  • PerlFixupHandler
  • PerlHandler
  • PerlLogHandler
  • PerlCleanupHandler

mod_perl 还提供了一些额外的 Handler:

  • PerlChildInitHandler
  • PerlChildExitHandler
  • PerlRestartHandler
  • PerlDispatchHandler

默认情况下, 大部分的 Handler 是 disable 的, 需要在编译 mod_perl 时启用.

The handler() subroutine

handler() 会在接收到 requests 时自动调用, 在一个 module 中定义了 handler() 后, 可以通过:

1
PerlHandler Apache::Foo

来使用, 但其不会预加载 Apache::Foo.

handler() 函数接收两个参数:$r$args。其中,$r 是一个 Apache::RequestRec 对象,代表当前的请求对象,它包含了与请求相关的信息, 例如请求的 URI, 请求的头部信息等. $args 则是一个字符串, 包含了请求的查询参数.

若想预加载, 可以:

1
PerlModule Apache::Foo

或者在 startup.pl 中加入:

1
use Apache::Foo ();

或者写为:

1
PerlHandler +Apache::Foo

其相当于:

1
2
3
4
5
PerlModule Apache::Foo
<Location ..>
...
PerlHandler Apache::Foo
</Location>

的 alias.

若想使用 handler() 以外的名称如 my_handler(), 可以写为:

1
2
3
4
5
PerlModule Apache::Foo
<Location ..>
...
PerlHandler Apache::Foo::my_handler
</Location>

查看当前运行的 handler:

1
2
3
if ($r->current_callback eq "PerlLogHandler") {
$r->warn("Logging request");
}

mod_perl
http://example.com/2023/10/16/mod-perl/
作者
Jie
发布于
2023年10月16日
许可协议