Perl CGI 编程
Perl - CGI 编程(历史背景和现代替代方案)
Section titled “Perl - CGI 编程(历史背景和现代替代方案)”**重要提示:**CGI(Common Gateway Interface,通用网关接口)是一种较旧的 Web 应用程序开发技术。虽然 Perl 曾是 CGI 脚本的主要语言,但现代 Perl Web 开发主要使用基于 PSGI/Plack 标准的框架,如 Mojolicious、Dancer2 或 Catalyst。这些框架提供更好的性能、可扩展性、安全性和开发实践。本章介绍 CGI 是为了历史理解或非常简单的用例。
什么是 CGI?
Section titled “什么是 CGI?”- CGI 是一个标准协议,定义了 Web 服务器如何执行外部程序(CGI 脚本)来生成动态 Web 内容。
- 当 Web 服务器收到对 CGI 脚本的请求时,它会执行该脚本并将输出传递回客户端的浏览器。
Web 浏览和 CGI
Section titled “Web 浏览和 CGI”通常,当您请求一个静态文件(例如 HTML 页面或图片)时:
- 您的浏览器向 Web 服务器发送一个 HTTP 请求。
- Web 服务器找到该文件,并在 HTTP 响应中将其内容发送回浏览器。
- 浏览器显示内容。
使用 CGI 时:
- 浏览器请求指向 CGI 脚本的 URL。
- Web 服务器识别它是一个 CGI 脚本,执行它,并将客户端信息(如表单数据)传递给脚本(通常通过环境变量和标准输入 STDIN)。
- CGI 脚本处理信息,并通过打印到其标准输出 STDOUT 生成 HTTP 响应(头信息和内容,通常是 HTML)。
- Web 服务器捕获此输出并将其发送回浏览器。
Web 服务器配置 CGI
Section titled “Web 服务器配置 CGI”要运行 CGI 脚本,您的 Web 服务器(例如 Apache, Nginx)必须配置为支持 CGI。这通常包括:
- 指定一个特定的目录(通常是
/cgi-bin/或类似名称)来存储 CGI 脚本。 - 确保该目录中的 Perl 脚本具有执行权限(例如,
chmod 755 script.cgi)。 - 配置服务器将该目录中具有特定扩展名(例如
.cgi,.pl)的文件视为可执行的 CGI 程序。
第一个 CGI 程序(使用 CGI.pm)
Section titled “第一个 CGI 程序(使用 CGI.pm)”CGI.pm 模块简化了许多常见的 CGI 任务,例如解析输入和生成 HTML。虽然它曾经是一个核心模块,但现在通常需要从 CPAN 安装。
#!/usr/bin/perluse 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 页面。
HTTP 头信息
Section titled “HTTP 头信息”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" | 提示浏览器将内容下载为文件。 |
CGI 环境变量
Section titled “CGI 环境变量”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_NAME | CGI 脚本的路径。 |
SERVER_NAME | 服务器的主机名。 |
示例:显示环境变量(使用 CGI.pm)
#!/usr/bin/perluse 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 方法
Section titled “GET 和 POST 方法”浏览器主要使用 GET 或 POST 方法向 Web 服务器(以及 CGI 脚本)发送数据。
GET 方法
Section titled “GET 方法”数据作为查询字符串附加到 URL 后(例如 script.cgi?name=Alice&age=30)。可以通过 $ENV{QUERY_STRING} 访问,或者使用 CGI.pm 更方便地通过 $q->param('name') 访问。适用于少量非敏感数据。受 URL 长度限制。
POST 方法
Section titled “POST 方法”数据在 HTTP 请求的主体中发送。原始 POST 数据从 STDIN 读取,长度由 $ENV{CONTENT_LENGTH} 提供。CGI.pm 通过 $q->param('name') 透明地处理此解析。适用于大量数据或敏感信息。
使用 CGI.pm 处理表单数据
Section titled “使用 CGI.pm 处理表单数据”无论使用 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/perluse 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("您没有选择任何兴趣。"); # 如果没有选定的兴趣,打印提示}在 CGI 中使用 Cookie
Section titled “在 CGI 中使用 Cookie”Cookie 是由浏览器存储的小块数据,在后续请求中发送回服务器。CGI.pm 提供了设置和检索 Cookie 的方法。
设置 Cookie:
#!/usr/bin/perluse strict;use warnings;use CGI;
my $q = CGI->new;
# 创建一个 cookiemy $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/perluse 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("没有文件上传。"); # 打印没有文件上传的消息}安全注意事项
Section titled “安全注意事项”编写 CGI 脚本(或任何 Web 应用程序)时:
- 验证所有输入: 绝不要信任来自客户端的数据。
- 对输出进行转义: 使用像
CGI::escapeHTML这样的函数来防止在显示用户数据时发生 XSS。 - 防止 SQL 注入: 如果与数据库交互,请使用 DBI 的占位符。
- 污点模式(Taint Mode): 使用
-T标志(#!/usr/bin/perl -T)运行 Perl 脚本以启用污点检查,这有助于识别不安全的数据流。 - 限制权限: 确保 CGI 脚本以最少的必要权限运行。
如前所述,对于新项目,强烈建议使用现代 Perl Web 框架(Mojolicious, Dancer2, Catalyst)而不是原始 CGI 脚本,以获得更好的安全性、可维护性和性能。