如何解决我的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(如PATHLD_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 (根据需要设置为GETHEADPOST
  • 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.pmCGI::RequestCGI::SimpleCGI::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也值得一提。

你在debugging的时候是否使用了error handling程序?

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的工具,为此。

我想知道没有人提到称为RemotePortPERLDB_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


现在,要用RemotePortdebugging这个脚本,首先我们需要一个networking监听器 ,通过它我们将与Perldebugging器交互; 我们可以使用命令行工具netcatnc ,在这里看到: Perl如何远程debugging? )。 因此,首先在一个terminal上运行netcat监听器 – 它会阻塞并等待7234端口(这将是我们的debugging端口)上的连接:

 $ nc -l 7234 

然后,当test.pl被调用时(甚至在CGI模式下,通过服务器),我们希望perlRemotePortdebugging模式启动。 这在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,等待数据。 我们现在可以切换到netcatterminal,我们应该看到熟悉的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)。

那么,现在我们可以通过ncterminal完成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代码的结果,如果有的话,会报告错误。