Skip to content

Perl CGI 编程

Perl - CGI 编程(历史背景和现代替代方案)

Section titled “Perl - CGI 编程(历史背景和现代替代方案)”

**重要提示:**CGI(Common Gateway Interface,通用网关接口)是一种较旧的 Web 应用程序开发技术。虽然 Perl 曾是 CGI 脚本的主要语言,但现代 Perl Web 开发主要使用基于 PSGI/Plack 标准的框架,如 Mojolicious、Dancer2 或 Catalyst。这些框架提供更好的性能、可扩展性、安全性和开发实践。本章介绍 CGI 是为了历史理解或非常简单的用例。

  • CGI 是一个标准协议,定义了 Web 服务器如何执行外部程序(CGI 脚本)来生成动态 Web 内容。
  • 当 Web 服务器收到对 CGI 脚本的请求时,它会执行该脚本并将输出传递回客户端的浏览器。

通常,当您请求一个静态文件(例如 HTML 页面或图片)时:

  • 您的浏览器向 Web 服务器发送一个 HTTP 请求。
  • Web 服务器找到该文件,并在 HTTP 响应中将其内容发送回浏览器。
  • 浏览器显示内容。

使用 CGI 时:

  • 浏览器请求指向 CGI 脚本的 URL。
  • Web 服务器识别它是一个 CGI 脚本,执行它,并将客户端信息(如表单数据)传递给脚本(通常通过环境变量和标准输入 STDIN)。
  • CGI 脚本处理信息,并通过打印到其标准输出 STDOUT 生成 HTTP 响应(头信息和内容,通常是 HTML)。
  • Web 服务器捕获此输出并将其发送回浏览器。

要运行 CGI 脚本,您的 Web 服务器(例如 Apache, Nginx)必须配置为支持 CGI。这通常包括:

  • 指定一个特定的目录(通常是 /cgi-bin/ 或类似名称)来存储 CGI 脚本。
  • 确保该目录中的 Perl 脚本具有执行权限(例如,chmod 755 script.cgi)。
  • 配置服务器将该目录中具有特定扩展名(例如 .cgi, .pl)的文件视为可执行的 CGI 程序。

CGI.pm 模块简化了许多常见的 CGI 任务,例如解析输入和生成 HTML。虽然它曾经是一个核心模块,但现在通常需要从 CPAN 安装。

#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $q = CGI->new; # 创建一个新的 CGI 对象
# 打印 HTTP 头信息
print $q->header('text/html');
# 打印 HTML 内容
print $q->start_html('Hello World - 第一个 CGI 程序');
print $q->h1('Hello World! 这是我的第一个使用 CGI.pm 的 CGI 程序');
print $q->end_html;

如果将此脚本(hello.cgi)放置在配置好的 cgi-bin 目录中并通过浏览器访问,它将输出一个包含 “Hello World” 消息的 HTML 页面。

CGI 脚本必须首先输出 HTTP 头信息,后跟一个空行,然后才是实际内容。最重要的头信息是 Content-Type,它告诉浏览器期望的数据类型(例如 text/html, image/png, application/json)。

手动打印头信息(不使用 CGI.pm):
print "Content-Type: text/html\n\n"; # 注意两个换行符

常见的 CGI HTTP 头信息:

头信息描述
Content-Type: type/subtype内容的 MIME 类型(例如,text/html, image/png, application/json)。
Location: URL将浏览器重定向到不同的 URL(与 3xx 状态码一起使用)。
Set-Cookie: name=value; attributes向浏览器发送一个 Cookie。
Content-Disposition: attachment; filename="file.ext"提示浏览器将内容下载为文件。

Web 服务器通过环境变量将信息传递给 CGI 脚本。CGI.pm 通常抽象了对这些变量的访问,但了解它们很有用。

变量名描述
REQUEST_METHOD使用的 HTTP 方法(例如,GET, POST)。
QUERY_STRING通过 GET 请求发送的 URL 编码数据(在 ? 之后)。
CONTENT_LENGTH通过 POST 请求发送的数据长度(从 STDIN 读取)。
CONTENT_TYPE通过 POST 请求发送的数据的 MIME 类型(例如,application/x-www-form-urlencoded)。
REMOTE_ADDR客户端的 IP 地址。
HTTP_USER_AGENT浏览器/客户端标识字符串。
SCRIPT_NAMECGI 脚本的路径。
SERVER_NAME服务器的主机名。

示例:显示环境变量(使用 CGI.pm)

#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $q = CGI->new;
print $q->header('text/html');
print $q->start_html('CGI 环境变量');
print $q->h1('CGI 环境变量');
print $q->start_table({-border => 1});
print $q->Tr($q->th(['变量', '值']));
foreach my $key (sort keys %ENV) {
print $q->Tr($q->td([$key, $ENV{$key}]));
}
print $q->end_table;
print $q->end_html;

浏览器主要使用 GET 或 POST 方法向 Web 服务器(以及 CGI 脚本)发送数据。

数据作为查询字符串附加到 URL 后(例如 script.cgi?name=Alice&age=30)。可以通过 $ENV{QUERY_STRING} 访问,或者使用 CGI.pm 更方便地通过 $q->param('name') 访问。适用于少量非敏感数据。受 URL 长度限制。

数据在 HTTP 请求的主体中发送。原始 POST 数据从 STDIN 读取,长度由 $ENV{CONTENT_LENGTH} 提供。CGI.pm 通过 $q->param('name') 透明地处理此解析。适用于大量数据或敏感信息。

无论使用 GET 还是 POST 方法,CGI.pm 都使得表单处理变得容易。

HTML 表单示例:

<form action="/cgi-bin/submit_form.cgi" method="POST">
First Name: <input type="text" name="first_name"><br>
Last Name: <input type="text" name="last_name"><br>
<input type="submit" value="Submit">
</form>

Perl CGI 脚本(submit_form.cgi):

#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $q = CGI->new;
# 获取表单参数
my $first_name = $q->param('first_name');
my $last_name = $q->param('last_name');
print $q->header('text/html');
print $q->start_html('表单提交结果');
print $q->h1('已收到提交');
if ($first_name && $last_name) {
print $q->p("您好," . $q->escapeHTML($first_name) . " " . $q->escapeHTML($last_name) . "!");
} else {
print $q->p("请提供您的名字和姓氏。");
}
print $q->end_html;

注意:escapeHTML() 对于在显示用户提供的数据时防止跨站脚本(XSS)漏洞至关重要。

处理复选框、单选按钮、文本区、下拉列表

Section titled “处理复选框、单选按钮、文本区、下拉列表”

CGI.pm 的 param() 方法正确处理这些输入类型:

  • 单值字段(text, radio, textarea, single-select dropdown): $q->param('fieldname') 返回单个值。
  • 多值字段(同名的复选框,多选下拉列表): 在列表上下文中,my @values = $q->param('fieldname') 返回所有选定的值。在标量上下文中,它通常返回第一个选定的值。

示例(复选框):

HTML:
<input type="checkbox" name="interests" value="music"> Music
<input type="checkbox" name="interests" value="sports"> Sports
Perl:
my @interests = $q->param('interests'); # 获取选定的兴趣列表
if (@interests) {
print $q->p("您的兴趣: " . $q->escapeHTML(join(', ', @interests))); # 如果有选定的兴趣,打印出来
} else {
print $q->p("您没有选择任何兴趣。"); # 如果没有选定的兴趣,打印提示
}

Cookie 是由浏览器存储的小块数据,在后续请求中发送回服务器。CGI.pm 提供了设置和检索 Cookie 的方法。

设置 Cookie:

#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $q = CGI->new;
# 创建一个 cookie
my $cookie = $q->cookie(
-name => 'UserID',
-value => 'XYZ123',
-expires => '+30d', # 30 天后过期
-path => '/'
);
# 打印包含 cookie 的头信息
print $q->header(-cookie => $cookie, -type => 'text/html');
print $q->start_html('Cookie 已设置');
print $q->p('UserID cookie 已经设置。');
print $q->end_html;

检索 Cookie:

#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $q = CGI->new;
my $user_id = $q->cookie('UserID'); # 检索 cookie 值
print $q->header('text/html');
print $q->start_html('Cookie 已检索');
if (defined $user_id) {
print $q->p("检索到的 UserID: " . $q->escapeHTML($user_id)); # 如果找到 cookie,打印值
} else {
print $q->p('UserID cookie 未找到。'); # 如果未找到 cookie,打印提示
}
print $q->end_html;

CGI.pm 还可以处理来自带有 enctype="multipart/form-data" 的表单的文件上传。

用于文件上传的 HTML:
<form method="POST" action="/cgi-bin/upload.cgi" enctype="multipart/form-data">
File: <input type="file" name="uploaded_file">
<input type="submit" value="Upload">
</form>
Perl (`upload.cgi`):
my $filename = $q->param('uploaded_file');
if ($filename) {
my $fh = $q->upload('uploaded_file'); # 上传的临时文件的文件句柄
if ($fh) {
# 处理文件,例如保存它
# open(my $out_fh, '>', "/path/to/save/$filename") or die "无法保存文件: $!";
# binmode $out_fh;
# while (my $bytes_read = read($fh, my $buffer, 4096)) {
# print $out_fh $buffer;
# }
# close $out_fh;
print $q->p("文件 '$filename' 上传成功 (理论上)。"); # 打印成功消息
} else {
print $q->p("获取 '$filename' 的上传句柄出错。"); # 打印错误消息
}
} else {
print $q->p("没有文件上传。"); # 打印没有文件上传的消息
}

编写 CGI 脚本(或任何 Web 应用程序)时:

  • 验证所有输入: 绝不要信任来自客户端的数据。
  • 对输出进行转义: 使用像 CGI::escapeHTML 这样的函数来防止在显示用户数据时发生 XSS。
  • 防止 SQL 注入: 如果与数据库交互,请使用 DBI 的占位符。
  • 污点模式(Taint Mode): 使用 -T 标志(#!/usr/bin/perl -T)运行 Perl 脚本以启用污点检查,这有助于识别不安全的数据流。
  • 限制权限: 确保 CGI 脚本以最少的必要权限运行。

如前所述,对于新项目,强烈建议使用现代 Perl Web 框架(Mojolicious, Dancer2, Catalyst)而不是原始 CGI 脚本,以获得更好的安全性、可维护性和性能。