Perl 和 CGI

CGI 代表 通用网关接口,它是一种通过网页请求执行脚本的协议,在20世纪90年代末是编写Web动态程序的主要方式。它也是我们使用的 Perl 模块 的名称(对我来说,现在还在使用)来编写Web代码。

警告 你可能不想在现代Web开发中使用 CGI,请参阅 为什么不要使用 CGI

CGI 和 HTTP

你可能听说过 HTTP(超文本传输协议),这是大多数互联网服务使用的通信协议。从广义上讲,CGI 程序接收 HTTP 请求,并返回 HTTP 响应。HTTP 响应头必须包括状态和内容类型。CGI(接口)使这变得简单。

我们可以将 Perl 脚本硬编码以返回 HTTP 响应头和 HTML 主体

#!/usr/bin/env perl
use strict;
use warnings;
print <<'END';
Status: 200
Content-type: text/html

<!doctype html>
<html> HTML Goes Here </html>
END

但 CGI.pm 可以为我们处理头信息

#!/usr/bin/env perl
use strict;
use warnings;
use CGI;
my $cgi = CGI->new();
print $cgi->header;

print <<'END';
<!doctype html>
<html> HTML Goes Here </html>
END

当然,你不必只发送 HTML 文本。

#!/usr/bin/env perl
use strict;
use warnings;
use CGI;
my $cgi = CGI->new();
print $cgi->header( -type => 'text/plain' );

print <<'END';
This is now text
END

但这绝非极限。内容类型是一个 多用途互联网邮件扩展(MIME)类型,它决定了浏览器返回消息后如何处理。上面的示例将“这是现在文本”消息视为文本,并按文本显示。如果内容类型是“text/html”,它将被解析为 HTML,就像网页一样。如果它是“application/json”,它可能像文本一样显示,或者根据你的浏览器或扩展进行格式化。如果它是“application/vnd.ms-excel”或甚至是“text/csv”,浏览器很可能会在 Excel 或其他电子表格程序中打开,或者直接在基因测序器中打开,就像我在工作中生成的那样。

如果程序是这样——

#!/usr/bin/env perl
use strict;
use warnings;
use CGI;
my $cgi = CGI->new();
print $cgi->header( -type => 'image/jpg' );
open my $img, '<', '/home/user/images/author/dave-jacoby.jpg';
while (<$img>) { print }

——你就会得到这个

/images/author/dave-jacoby.jpg

处理输入

传递数据的第一个方法是使用查询字符串(URI开头的部分),你可以在如 https://example.com/?foo=bar 这样的 URL 中看到。这使用“GET”请求方法,并且作为 $ENV->{QUERY_STRING} 对程序可用,在这种情况下是 foo=bar(CGI程序接收它们的参数作为环境变量)。但 CGI 提供了 param 方法,它将查询字符串解析为键值对,因此你可以像使用散列一样处理它们。

#!/usr/bin/perl
use strict;
use warnings;
use CGI;

my $cgi = CGI->new;
my %param = map { $_ => scalar $cgi->param($_) } $cgi->param() ;
print $cgi->header( -type => 'text/plain' );
print qq{PARAM:\N};
for my $k ( sort keys %param ) {
    print join ": ", $k, $param{$k};
    print "\n";
}
# PARAM:
# foo: bar

现在,让我们创建一个像这样的网页

<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <form method="POST" action="/url/of/simple.cgi">
        <input type="text" name="foo" value="bar">
        <input type="submit">
    </form>
</body>
</html>

然后点击提交。浏览器将发送一个 HTTP “POST” 请求,其中表单输入作为请求体中的键值对。CGI 处理这个问题,并将数据放在 $cgi->param 中,就像“GET”一样。只是“POST”的输入大小可以大得多(浏览器通常限制 URL 大小为 2048 字节)。

生成 HTML

让我们使用 CGI 附件的 HTML 生成技术制作上面的表单。

my $output;
$output .= $cgi->start_form(
    -method => "post",
    -action => "/path/to/simple.cgi"
);
print $cgi->textfield( -name => 'foo', -value => 'bar' );
print $cgi->submit;
print $cgi->end_form;

但问题是使用 CGI 生成 HTML 的代码可能会变得非常长,难以阅读。CGI 的维护者也同意这一点,这就是为什么这是 CGI.pm 文档的开头 的原因。

CGI.pm 中的所有 HTML 生成函数将不再维护。[...] 维护此决定的原因是 CGI.pm 的 HTML 生成函数最多是模糊不清,最坏的情况是维护噩梦。您应该使用模板引擎来更好地分离关注点。请参见 CGI::Alternatives,了解如何使用 Template::Toolkit 模块与 CGI.pm 一起使用。

使用 Template Toolkit,该表单可能看起来像

#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use Template;

my $cgi      = CGI->new;
my $template = Template->new();
my $input    = join "\n", <DATA>;
my $data     = { action => '/url/of/program'} ;

print $cgi->header;
$template->process(\$input,$data)
    || die "Template process failed", $template->error();

__DATA__
    <form method="POST" action="[% action %]">
        <input type="text" name="foo" value="bar">
        <input type="submit">
    </form>

我为所有服务器端网络工作使用 Template Toolkit。它也是 Perl 许多网络框架的默认选择。

在 Apache 上配置 CGI

要使用 CGI,您的 Web 服务器应已安装 mod_cgi。一旦安装,您需要配置您的服务器以执行 CGI 程序。

第一种方式是拥有 cgi-bin 目录,其中每个文件都会被执行而不是传输。

<Directory "/home/*/www/cgi-bin">
    Options ExecCGI
    SetHandler cgi-script
</Directory>

另一种方式是允许 CGI 按目录启用,配置如下所示

<Directory "/home/*/www">
    Options +ExecCGI
    AddHandler cgi-script .cgi
</Directory>

然后在每个目录中添加一个类似这样的 .htaccess 文件

AddHandler cgi-script .cgi
Options +ExecCGI

这样 foo.pl 会传输,而 foo.cgi 会运行,即使两者都可执行。

为什么不使用 CGI

2013 年 5 月,当时的 Perl5 Pumpking Ricardo Signes 将以下内容发送到 Perl5 Porters 列表:

我认为现在是认真考虑从核心发行版中删除 CGI.pm 的时候了。它不再是任何人在编写任何类型网络代码时应该指向的东西。据我所知,它仍然在核心中,因为它曾经是业界领先的技术,也是许多人使用该语言的主要原因。我认为现在这两者都不再成立。

它在 5.20 版本中被标记为已弃用,并在 5.22 版本中从核心中删除。这不是灾难性的;它仍然可在 CPAN 中找到,因此您可能需要安装它,或者根据您的具体情况,由您的管理员安装。

那么,为什么 CGI 从“业界领先的技术”降至其维护者所不推荐的技术呢?

CGI 有两个主要问题:速度和复杂性。每个 HTTP 请求都会在 Web 服务器上触发新进程的派生,这对服务器资源来说代价高昂。一种更有效、更快的做法是使用一个多进程守护进程,它在启动时进行派生,并维护一个进程池来处理请求。

CGI 在管理大型 Web 应用的复杂性方面并不出色:它没有 MVC 架构来帮助开发者分离关注点。这往往会导致难以维护的程序。

像 Ruby on Rails 这样的 Web 框架及其运行的应用程序服务器的兴起,在解决这两个问题上做出了很大贡献。有许多用 Perl 编写的 Web 框架;其中最受欢迎的是 CatalystDancerMojolicious

CGI 还包含一个安全 漏洞,必须通过编码来规避参数注入。

参考文献

CGI.pm 的“良好”部分,包括头部创建和参数解析,在模块的 文档 中有很好的解释。至于已弃用的 HTML 生成函数,它们有单独的 文档

CGI.pm 的创建者 Lincoln Stein 还写了 官方指南。这本书已经 20 年了,已经过时,但仍然是关于 CGI.pm 的一个清晰、简洁的资源。

CGI.pm 的当前维护者 Lee Johnson 写了一篇关于 CGI 历史、当前状态和未来的长博客 文章

标签

Dave Jacoby

Dave是Purdue Perl Mongers的策划人,并在普渡大学担任软件工程师

浏览他们的文章

反馈

这篇文章有什么问题吗?请通过在GitHub上提交问题或拉取请求来帮助我们