如何解决我的Perl CGI脚本?
我有一个Perl脚本不工作,我不知道如何开始缩小这个问题。 我能做什么?
注意:我添加了这个问题,因为我真的想把我很长的答案添加到Stackoverflow。 我保持外部链接到其他答案,它值得在这里。 如果你有要添加的东西,不要害怕编辑我的答案。
这个答案的目的是作为一个通用的框架来解决Perl CGI脚本的问题,最初出现在Perlmonks上,作为对Perl CGI脚本的疑难解答 。 这不是你可能遇到的每个问题的完整指南,也不是一个关于错误压缩的教程。 这只是我debuggingCGI脚本十年(加!)年的经验的高潮。 这个页面似乎有很多不同的家园,我似乎忘记它存在,所以我把它添加到StackOverflow。 您可以发送任何意见或build议给我在bdfoy@cpan.org。 这也是社区维基,但不要太疯狂。 🙂
您是否使用Perl的内置function来帮助您发现问题?
打开警告,让Perl警告您关于代码的可疑部分。 你可以在命令行中用-w
开关来做到这一点,所以你不需要改变任何代码或者为每个文件添加一个编译指示:
% perl -w program.pl
但是,您应该强制自己始终通过向所有文件添加warnings
杂注来清除有问题的代码:
use warnings;
如果您需要比简短警告消息更多的信息,请使用diagnostics
pragma获取更多信息,或者查看perldiag文档:
use diagnostics;
你有没有输出一个有效的CGI头?
服务器期望CGI脚本的第一个输出是CGI头。 通常这可能与print "Content-type: text/plain\n\n";
或者用CGI.pm及其衍生物print header()
。 某些服务器对标准输出(在STDOUT
)之前显示的错误输出(在STDERR
)很敏感。
尝试将错误发送到浏览器
添加行
use CGI::Carp 'fatalsToBrowser';
到你的脚本。 这也会将编译错误发送到浏览器窗口。 请务必在移至生产环境之前将其删除,因为额外的信息可能存在安全风险。
错误日志说了什么?
服务器保存错误日志(或者至less应该)。 从服务器和脚本输出的错误应该显示在那里。 find错误日志,看看它说什么。 没有一个标准的日志文件的地方。 查看服务器configuration中的位置,或询问服务器pipe理员。 您也可以使用CGI :: Carp等工具来保存自己的日志文件。
脚本的权限是什么?
如果您看到“权限被拒绝”或“方法未执行”等错误,则可能意味着您的脚本无法被Web服务器用户读取和执行。 在Unix的口味上,build议将模式更改为755: chmod 755 filename
。 切勿将模式设置为777!
你use strict
吗?
请记住,第一次使用Perl时,Perl会自动创buildvariables。 这是一个function,但是如果你输错了一个variables名称,有时会引起错误。 use strict
的编译指示将帮助您find这些错误。 这很烦人,直到你习惯了,但一段时间后,你的编程将会显着提高,你将可以自由地犯出不同的错误。
脚本是否编译?
您可以使用-c
开关检查编译错误。 集中精力报道第一个错误。 冲洗,重复。 如果您遇到非常奇怪的错误,请检查以确保您的脚本具有正确的行结束符。 如果以二进制模式进行FTP,从CVS签出,或者其他不能处理行结束转换的东西,Web服务器可能会将您的脚本视为一个大问题。 以ASCII模式传输Perl脚本。
脚本是否抱怨不安全的依赖?
如果您的脚本抱怨不安全的依赖关系,您可能使用-T
开关来打开污点模式,这是一件好事,因为它可以让您将未经检查的数据传递到shell。 如果它正在抱怨它正在帮助我们编写更安全的脚本。 任何来自程序之外的数据(即环境)都被认为是污染的。 环境variables(如PATH
和LD_LIBRARY_PATH
特别麻烦。 按照我的build议,您必须将这些设置为安全值或完全取消设置。 无论如何,你应该使用绝对path。 如果污点检查抱怨其他事情,请确保您已经清除了数据。 有关详细信息,请参阅perlsec手册页。
当你从命令行运行它会发生什么?
当从命令行运行脚本时,脚本是否输出所期望的内容? 首先输出标题,然后是空白行? 请记住,如果您在terminal(例如交互式会话)上, STDERR
可能会与STDOUT
合并,并且由于缓冲可能以混乱的顺序显示。 通过设置$|
打开Perl的自动刷新function 到真正的价值。 通常你可能会看到$|++;
在CGI程序中。 一旦设置,每一个打印和写入将立即去输出,而不是被缓冲。 你必须为每个文件句柄设置它。 使用select
来更改默认的文件句柄,如下所示:
$|++; #sets $| for STDOUT $old_handle = select( STDERR ); #change to STDERR $|++; #sets $| for STDERR select( $old_handle ); #change back to STDOUT
无论哪种方式,输出的第一个东西应该是CGI头后跟一个空行。
当你使用类似CGI的环境从命令行运行时会发生什么?
Web服务器环境通常比命令行环境更受限制,并且具有关于请求的额外信息。 如果您的脚本在命令行中运行良好,则可以尝试模拟Web服务器环境。 如果出现问题,则说明存在环境问题。
取消设置或删除这些variables
-
PATH
-
LD_LIBRARY_PATH
- 所有的
ORACLE_*
variables
设置这些variables
-
REQUEST_METHOD
(根据需要设置为GET
,HEAD
或POST
) -
SERVER_PORT
(通常设置为80) -
REMOTE_USER
(如果您正在进行受保护的访问)
最近版本的CGI.pm
(> 2.75)需要使用-debug
标志来获取旧的(有用的)行为,因此您可能需要将其添加到CGI.pm
导入。
use CGI qw(-debug)
你使用die()
还是warn
?
这些函数打印到STDERR
除非你已经重新定义了它们。 它们也不输出CGI标题。 您可以获得与CGI :: Carp等软件包相同的function
清除浏览器caching后会发生什么?
如果您认为您的脚本正在做正确的事情,并且手动执行请求,则会得到正确的输出,浏览器可能是罪魁祸首。 清除caching并在testing时将caching大小设置为零。 请记住,一些浏览器是非常愚蠢的,即使你告诉它,实际上也不会重新加载新的内容。 这在URLpath相同但内容改变(例如dynamic图像)的情况下尤其普遍。
你认为这是脚本吗?
脚本的文件系统path不一定与脚本的URLpath直接相关。 确保你有正确的目录,即使你必须写一个简短的testing脚本来testing。 而且,你确定你正在修改正确的文件吗? 如果您对更改没有看到任何影响,则可能是修改了其他文件,或将file upload到了错误的地方。 (顺便说一句,这是我造成这种麻烦的最常见原因;)
你使用CGI.pm
还是其衍生物?
如果您的问题与parsingCGIinput有关,并且您没有使用CGI.pm
, CGI::Request
, CGI::Simple
或CGI::Lite
等广泛testing的模块,请使用该模块并继续使用。 CGI.pm
具有cgi-lib.pl
兼容性模式,可以帮助您解决由于较早的CGIparsing器实现而导致的input问题。
你使用绝对path吗?
如果您正在使用system
,回刻录机或其他IPC工具运行外部命令,则应该使用外部程序的绝对path。 您不仅确切知道您正在运行的是什么,而且还避免了一些安全问题。 如果打开文件以进行读取或写入,请使用绝对path。 CGI脚本可能对你当前的目录有不同的看法。 或者,你可以做一个明确的chdir()
把你放在正确的地方。
你检查了你的返回值吗?
大多数Perl函数会告诉你它们是否工作,并设置$!
失败。 你检查了返回值并检查$!
对于错误消息? 如果你使用eval
你检查$@
吗?
你正在使用哪个版本的Perl?
Perl的最新稳定版本是5.16.2。 你使用的是旧版本吗? 不同版本的Perl可能会有不同的警告。
你使用哪个networking服务器?
不同的服务器在相同的情况下可能会有所不同 相同的服务器产品可能会采取不同的configuration。 尽可能多地包含任何请求帮助的信息。
你检查了服务器文档吗?
严重的CGI程序员应该尽可能多地了解服务器 – 不仅包括服务器特性和行为,还包括本地configuration。 如果您使用的是商业产品,则可能无法使用您的服务器的文档。 否则,文档应该在您的服务器上。 如果不是,请在网上查找。
你有没有searchcomp.infosystems.www.authoring.cgi
的档案?
很可能以前有人遇到过你的问题,而且有人(可能是我)在这个新闻组中回答了这个问题。 虽然这个新闻组已经过了鼎盛时期,但从过去收集的智慧有时可能是有用的。
你能用一个简短的testing脚本重现问题吗?
在大型系统中,可能很难find一个错误,因为发生了很多事情。 尝试用尽可能短的脚本重现问题行为。 知道这个问题是最重要的。 这可能是非常耗时的,但是你还没有发现问题,而且你没有select。 🙂
你决定去看电影吗?
认真。 有时候我们可以把这个问题搞得一团糟,发展出“感性狭隘”(隧道视野)。 rest一下,喝一杯咖啡,或者在[Nukem,Quake,Doom,Halo,COD]爆破一些坏人,可能会给你一个新的观点,你需要重新处理这个问题。
你有没有发现这个问题?
再次认真。 有时候大声地解释这个问题会使我们得到自己的答案。 和企鹅(毛绒玩具)交谈,因为你的同事没在听。 如果你对这个作为一个严肃的debugging工具感兴趣,(如果你现在还没有发现问题,我推荐它),你也可以阅读“计算机编程心理学” 。
我认为CGI :: Debug也值得一提。
die
语句和其他致命的运行时间和编译时错误打印到STDERR
,这可能很难find,并可能与您的网站上的其他网页的消息混合。 当你在debugging你的脚本的时候,最好让你的浏览器以某种方式显示致命的错误信息。
一个办法是打电话
use CGI::Carp qw(fatalsToBrowser);
在脚本的顶部。 这个调用会在您的浏览器中安装一个$SIG{__DIE__}
处理程序(请参阅perlvar ),并在必要时在其前面添加一个有效的头文件。 在我听说过CGI::Carp
之前,另一个CGIdebugging技巧是使用eval
和脚本中的DATA
和__END__
工具来捕捉编译时错误:
#!/usr/bin/perl eval join'', <DATA>; if ($@) { print "Content-type: text/plain:\n\nError in the script:\n$@\n; } __DATA__ # ... actual CGI script starts here
这个更详细的技术比CGI::Carp
略有优势,因为它会捕获更多的编译时错误。
更新:我从来没有使用它,但它看起来像CGI::Debug
,如Mikael Sbuild议,也是一个非常有用的和可configuration的工具,为此。
我想知道没有人提到称为RemotePort
的PERLDB_OPTS
选项; 虽然可以RemotePort
是,网上没有太多的工作例子( RemotePort
甚至在RemotePort
都没有提到) – 对我来说这是一个问题,但在这里(它是一个Linux的例子)。
为了做一个恰当的例子,首先我需要一些可以对CGI Web服务器进行非常简单的模拟,最好是通过一个命令行。 find运行cgis的简单命令行web服务器后。 (perlmonks.org) ,我发现IO :: All – 一个微小的Web服务器适用于这个testing。
在这里,我将在/tmp
目录下工作。 CGI脚本将是/tmp/test.pl
(下面包含)。 请注意, IO::All
服务器只能在与CGI相同的目录中提供可执行文件,因此在这里需要chmod +x test.pl
因此,为了进行常规的CGItesting,我在terminal中将目录切换到/tmp
,并在那里运行单行web服务器:
$ cd /tmp $ perl -MIO::All -e 'io(":8080")->fork->accept->(sub { $_[0] < io(-x $1 ? "./$1 |" : $1) if /^GET \/(.*) / })'
webserver命令将在terminal中阻塞,否则将在本地启动Web服务器(在127.0.0.1或localhost
) – 之后,我可以访问Web浏览器并请求此地址:
http://127.0.0.1:8080/test.pl
…我应该观察到test.pl
在web浏览器中加载并显示的print
。
现在,要用RemotePort
debugging这个脚本,首先我们需要一个networking监听器 ,通过它我们将与Perldebugging器交互; 我们可以使用命令行工具netcat
( nc
,在这里看到: Perl如何远程debugging? )。 因此,首先在一个terminal上运行netcat
监听器 – 它会阻塞并等待7234端口(这将是我们的debugging端口)上的连接:
$ nc -l 7234
然后,当test.pl
被调用时(甚至在CGI模式下,通过服务器),我们希望perl
以RemotePort
debugging模式启动。 这在Linux中可以使用下面的“shebang wrapper”脚本来完成 – 这里也需要在/tmp
,并且必须是可执行的:
cd /tmp cat > perldbgcall.sh <<'EOF' #!/bin/bash PERLDB_OPTS="RemotePort=localhost:7234" perl -d -e "do '$@'" EOF chmod +x perldbgcall.sh
这是一个棘手的事情 – 请参阅shell脚本 – 我怎样才能在我的shebang使用环境variables? – Unix和Linux堆栈交换 。 但是,这里的诀窍似乎不是 fork perl
解释器来处理test.pl
– 所以一旦我们打开它,我们不会exec
,而是我们调用perl
“明白地”,并基本上“源”我们的test.pl
脚本使用do
(请参阅如何从Perl脚本中运行Perl脚本? )。
现在我们在/tmp
有了perldbgcall.sh
– 我们可以改变test.pl
文件,这样它就可以在它的shebang行(而不是通常的Perl解释器)上引用这个可执行文件 – 这里是/tmp/test.pl
修改从而:
#!./perldbgcall.sh # this is test.pl use 5.10.1; use warnings; use strict; my $b = '1'; my $a = sub { "hello $b there" }; $b = '2'; print "YEAH " . $a->() . " CMON\n"; $b = '3'; print "CMON " . &$a . " YEAH\n"; $DB::single=1; # BREAKPOINT $b = '4'; print "STEP " . &$a . " NOW\n"; $b = '5'; print "STEP " . &$a . " AGAIN\n";
现在, test.pl
和新的shebang处理程序perldbgcall.sh
都在/tmp
; 我们有nc
监听端口7234上的debugging连接 – 所以我们终于可以打开另一个terminal窗口,将目录切换到/tmp
,然后运行单线程web服务器(它将在端口8080上监听web连接):
cd /tmp perl -MIO::All -e 'io(":8080")->fork->accept->(sub { $_[0] < io(-x $1 ? "./$1 |" : $1) if /^GET \/(.*) / })'
完成后,我们可以到我们的网页浏览器,并请求相同的地址, http://127.0.0.1:8080/test.pl
。 但是,现在,当Web服务器尝试执行脚本时,它将通过perldbgcall.sh
shebang执行 – 这将以远程debugging器模式启动perl
。 因此,脚本执行将暂停 – 所以Web浏览器将locking,等待数据。 我们现在可以切换到netcat
terminal,我们应该看到熟悉的Perldebugging器文本 – 但是,通过nc
输出:
$ nc -l 7234 Loading DB routines from perl5db.pl version 1.32 Editor support available. Enter h or `hh' for help, or `man perldebug' for more help. main::(-e:1): do './test.pl' DB<1> r main::(./test.pl:29): $b = '4'; DB<1>
正如代码片段所示,我们现在基本上使用nc
作为“terminal” – 所以我们可以inputr
(和Enter)作为“run” – 脚本将运行起来做断点语句(另请参阅在perl中,有什么区别在$ DB :: single = 1和2之间 ),然后再次停止(注意在这一点上,浏览器将仍然locking)。
那么,现在我们可以通过nc
terminal完成test.pl
的其余部分:
.... main::(./test.pl:29): $b = '4'; DB<1> n main::(./test.pl:30): print "STEP " . &$a . " NOW\n"; DB<1> n main::(./test.pl:31): $b = '5'; DB<1> n main::(./test.pl:32): print "STEP " . &$a . " AGAIN\n"; DB<1> n Debugged program terminated. Use q to quit or R to restart, use o inhibit_exit to avoid stopping after program termination, hq, h R or ho to get additional info. DB<1>
…但是,在这一点上,浏览器locking并等待数据。 只有当我们退出debugging器q
:
DB<1> q $
浏览器停止locking – 最后显示test.pl
的(完整)输出:
YEAH hello 2 there CMON CMON hello 3 there YEAH STEP hello 4 there NOW STEP hello 5 there AGAIN
当然,即使不运行Web服务器也可以完成这种debugging – 然而,这里整洁的事情是我们根本不接触Web服务器; 我们从networking浏览器触发“原生”(对于CGI) – CGI脚本本身需要的唯一改变是shebang的改变(当然,也包括shebang包装脚本的存在,因为可执行文件在相同的目录)。
那么,希望这有助于某人 – 我肯定会爱上这个,而不是自己写的:)
干杯!
对我来说,我使用log4perl 。 这非常有用和简单。
use Log::Log4perl qw(:easy); Log::Log4perl->easy_init( { level => $DEBUG, file => ">>d:\\tokyo.log" } ); my $logger = Log::Log4perl::get_logger(); $logger->debug("your log message");
老实说,你可以做这个职位以上的所有有趣的东西。 尽pipe如此,我发现的最简单,最积极的解决scheme就是“打印”。
例如:(普通代码)
`$somecommand`;
看看它是否正在做我真正想做的事:(故障排除)
print "$somecommand";
也许值得一提的是,当你从命令行执行Perl脚本时,Perl总是会告诉你错误发生在哪一行。 (以SSH会话为例)
如果一切都失败了,我通常会这样做。 我将SSH进入服务器并手动执行Perl脚本。 例如:
% perl myscript.cgi
如果有问题,那么Perl会告诉你。 这种debugging方法消除了任何与文件权限相关的问题或者Web浏览器或Web服务器问题。
您可以使用以下命令在terminal中运行perl cgi-script
$ perl filename.cgi
它解释代码并提供HTML代码的结果,如果有的话,会报告错误。