如何解决Perl CGI脚本问题?

bri*_*foy 99 perl cgi

我有一个不工作的Perl脚本,我不知道如何开始缩小问题范围.我能做什么?


注意:我正在添加问题,因为我真的想要将非常冗长的答案添加到Stackoverflow.我在其他答案中保持外部链接,它值得在这里.如果你有什么要补充的话,不要害怕编辑我的答案.

bri*_*foy 125

这个答案旨在作为解决Perl CGI脚本问题的一般框架,最初出现在Perlmonks上作为故障排除Perl CGI脚本.它不是您可能遇到的每个问题的完整指南,也不是关于破坏bug的教程.这是我调试CGI脚本20年(加!)年的经验的高潮.这个页面似乎有许多不同的家,我似乎忘了它存在,所以我将它添加到StackOverflow.您可以通过bdfoy@cpan.org向我发送任何意见或建议.它也是社区维基,但不要太疯狂.:)


您是否使用Perl的内置功能来帮助您找到问题?

打开警告,让Perl警告您有关代码的可疑部分.您可以使用-w交换机从命令行执行此操作,这样您就不必更改任何代码或向每个文件添加编译指示:

 % perl -w program.pl
Run Code Online (Sandbox Code Playgroud)

但是,您应该强制自己通过将warningspragma 添加到所有文件来清除可疑代码:

 use warnings;
Run Code Online (Sandbox Code Playgroud)

如果您需要的信息多于短警告消息,请使用diagnosticspragma获取更多信息,或查看perldiag文档:

 use diagnostics;
Run Code Online (Sandbox Code Playgroud)

你首先输出了一个有效的CGI标题吗?

服务器期望CGI脚本的第一个输出是CGI头.通常,这可能print "Content-type: text/plain\n\n";CGI.pm及其衍生物一样简单print header().某些服务器对STDERR标准输出(打开STDOUT)之前出现的错误输出(打开)很敏感.

尝试向浏览器发送错误

添加此行

 use CGI::Carp 'fatalsToBrowser';
Run Code Online (Sandbox Code Playgroud)

到你的脚本.这也会将编译错误发送到浏览器窗口.在移至生产环境之前,请务必将其删除,因为额外信息可能存在安全风险.

错误日志说了什么?

服务器保留错误日志(或至少应该).从服务器和脚本输出的错误应该显示在那里.找到错误日志并查看其内容.日志文件没有标准位置.查看服务器配置中的位置,或询问服务器管理员.您还可以使用CGI :: Carp等工具 来保存自己的日志文件.

脚本的权限是什么?

如果您看到"权限被拒绝"或"未实现方法"等错误,则可能意味着您的脚本不可由Web服务器用户读取和执行.在Unix的风格上,建议将模式更改为755 : chmod 755 filename. 永远不要将模式设置为777!

你在用use strict吗?

请记住,Perl会在您第一次使用它们时自动创建变量.这是一个功能,但如果您输错变量名称,有时可能会导致错误.该编译指示 use strict将帮助您找到这些错误.在你习惯它之前很烦人,但是你的编程会在一段时间后显着改善,你可以自由地犯下不同的错误.

脚本是否编译?

您可以使用该-c 开关检查编译错误.专注于报告的第一个错误.冲洗,重复.如果您遇到非常奇怪的错误,请检查以确保您的脚本具有正确的行结尾.如果您在二进制模式下FTP,从CVS签出,或其他不能处理行结束转换的内容,Web服务器可能会将您的脚本视为一个大行.以ASCII模式传输Perl脚本.

脚本是否抱怨不安全的依赖?

如果您的脚本抱怨不安全的依赖项,您可能正在使用该-T开关打开污点模式,这是一件好事,因为它可以让您将未经检查的数据传递给shell.如果它抱怨它正在帮助我们编写更安全的脚本.源自程序外部(即环境)的任何数据都被视为污染.环境变量如PATHLD_LIBRARY_PATH 特别麻烦.您必须按照我的建议将它们设置为安全值或完全取消它们.无论如何你应该使用绝对路径.如果污点检查抱怨其他内容,请确保您没有污染数据.有关 详细信息,请参阅perlsec手册页.

从命令行运行它会发生什么?

脚本输出从命令行运行时的预期值吗?首先输出标题,然后是空白行?请记住, 如果您在终端(例如交互式会话),STDERR可能会合并STDOUT,并且由于缓冲可能会以混乱的顺序出现.通过设置$|为真值来启用Perl的autoflush功能.通常,您可能会$|++;在CGI程序中看到.一旦设置,每次打印和写入将立即转到输出而不是缓冲.您必须为每个文件句柄设置此项.使用select更改默认的文件句柄,就像这样:

$|++;                            #sets $| for STDOUT
$old_handle = select( STDERR );  #change to STDERR
$|++;                            #sets $| for STDERR
select( $old_handle );           #change back to STDOUT
Run Code Online (Sandbox Code Playgroud)

无论哪种方式,输出的第一件事应该是CGI标题后跟一个空行.

当您从具有CGI类似环境的命令行运行它时会发生什么?

Web服务器环境通常比命令行环境更受限制,并且具有有关请求的额外信息.如果脚本从命令行运行正常,则可以尝试模拟Web服务器环境.如果出现问题,则表明存在环境问题.

取消设置或删除这些变量

  • PATH
  • LD_LIBRARY_PATH
  • 所有ORACLE_*变量

设置这些变量

  • REQUEST_METHOD(设置为GET,HEADPOST适当时)
  • SERVER_PORT (通常设为80)
  • REMOTE_USER (如果你正在做受保护的访问的东西)

最新版本CGI.pm(> 2.75)要求-debug标志获取旧的(有用的)行为,因此您可能必须将其添加到CGI.pm导入中.

use CGI qw(-debug)
Run Code Online (Sandbox Code Playgroud)

你在用die()warn吗?

STDERR除非您重新定义它们,否则这些函数将打印到.它们也不输出CGI标题.您可以使用CGI :: Carp等软件包获得相同的功能

清除浏览器缓存后会发生什么?

如果您认为您的脚本正在做正确的事情,并且当您手动执行请求时,您将获得正确的输出,浏览器可能是罪魁祸首.清除缓存并在测试时将缓存大小设置为零.请记住,有些浏览器确实非常愚蠢,即使您告诉它,也不会重新加载新内容.这在URL路径相同但内容改变(例如动态图像)的情况下尤其普遍.

脚本是你认为的吗?

脚本的文件系统路径不一定与脚本的URL路径直接相关.确保您拥有正确的目录,即使您必须编写一个简短的测试脚本来测试它.此外,您确定要修改正确的文件吗?如果您没有看到对更改产生任何影响,则可能是在修改其他文件,或者将文件上传到错误的位置.(顺便说一句,这是我经常遇到这种麻烦的原因;)

您使用的CGI.pm是它的衍生物吗?

如果你的问题与解析CGI输入和你没有使用广泛的测试模块一样CGI.pm,CGI::Request, CGI::Simple或者CGI::Lite,使用模块和享受生活. CGI.pm有一个cgi-lib.pl兼容模式,可以帮助您解决由于旧的CGI解析器实现导致的输入问题.

你使用绝对路径了吗?

如果使用外部命令 system,后退标记或其他IPC工具运行,则应使用外部程序的绝对路径.您不仅知道自己正在运行的是什么,而且还避免了一些安全问题.如果要打开文件进行读取或写入,请使用绝对路径.CGI脚本可能对您当前目录有不同的想法.或者,您可以明确chdir()地将您带到正确的位置.

你检查过你的回报了吗?

大多数Perl函数会告诉您它们是否有效并且将会$!失败.您是否检查了返回值并检查$!错误消息?你有没有检查过 $@你是否在使用eval

您使用的是哪个版本的Perl?

Perl的最新稳定版本是5.28(或不是,取决于上次编辑的时间).你使用的是旧版本吗?不同版本的Perl可能有不同的警告概念.

您使用的是哪个Web服务器?

不同的服务器在相同的情况下可能表现不同.相同的服务器产品可能对不同的配置采取不同的行 在任何求助请求中尽可能多地包含此信息.

你检查过服务器文档了吗?

严肃的CGI程序员应该尽可能多地了解服务器 - 不仅包括服务器功能和行为,还包括本地配置.如果您使用的是商业产品,则可能无法使用您的服务器文档.否则,文档应该在您的服务器上.如果不是,请在网上查找.

你搜索过的档案了comp.infosystems.www.authoring.cgi吗?

这种用法很有用,但所有好的海报都要么死了,要么徘徊不去.

很可能有人之前遇到过您的问题,并且有人(可能是我)已在此新闻组中回答了问题.虽然这个新闻组已经过了鼎盛时期,但过去收集到的智慧有时会很有用.

你能用一个简短的测试脚本重现问题吗?

在大型系统中,由于发生了很多事情,因此可能很难找到错误.尝试使用尽可能短的脚本重现问题行为.了解问题是大多数问题.这可能肯定是耗时的,但是你还没有发现问题,而且你的选项已经用完了.:)

你决定去看电影吗?

认真.有时候我们可以解决这个问题,即我们开发出"感知缩小"(隧道视觉).在[Duke Nukem,Quake,Doom,Halo,COD]中休息一下,喝一杯咖啡或者爆破一些坏人可能会给你一个新的视角,你需要重新解决问题.

你有问题吗?

再说一遍.有时大声解释问题会引导我们找到自己的答案.和企鹅(毛绒玩具)交谈,因为你的同事没有听.如果你对这个作为一个严肃的调试工具感兴趣(如果你现在还没有找到问题,我建议你这样做),你可能还想阅读计算机程序设计心理学.

  • 如果你有什么要补充的话,不要害怕编辑我的答案. (4认同)
  • 很好的答案,我认为值得一提的是,其中一些解决方案应该纯粹用于故障排除而不是生产代码.`use strict`一般都很好用,而在生产过程中可能不建议使用`fatalsToBrowser`,特别是如果你使用`die`. (2认同)

Mik*_*l S 10

我认为CGI :: Debug也值得一提.


mob*_*mob 7

您在调试时是否使用错误处理程序?

die语句和其他致命的运行时和编译时错误被打印到STDERR,这很难找到,可能与您站点上其他网页的消息混淆.在调试脚本时,最好将某些致命的错误消息以某种方式显示在浏览器中.

一种方法是打电话

   use CGI::Carp qw(fatalsToBrowser);
Run Code Online (Sandbox Code Playgroud)

在脚本的顶部.该调用将安装一个$SIG{__DIE__}处理程序(请参阅perlvar),在浏览器中显示致命错误,必要时在前面添加有效标头.另外,我用我曾经听说过的CGI的调试技巧CGI::Carp是使用evalDATA__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
Run Code Online (Sandbox Code Playgroud)

这种更详细的技术略有优势CGI::Carp,因为它会捕获更多的编译时错误.

更新:我从未使用它,但看起来CGI::Debug,正如Mikael S建议的那样,它也是一个非常有用且可配置的工具.

  • @Ether:`<DATA>`是一个神奇的文件句柄,它以`__END__`开头读取当前脚本.Join正在提供列表上下文,因此<fh>返回一个数组,每个项目一行.然后加入将它重新组合在一起(与''连接).最后,评估. (3认同)

sda*_*aau 7

我想知道为什么没人提到这个PERLDB_OPTS选项RemotePort; 虽然不可否认,网上的工作实例并不多(RemotePort甚至在perldebug中都没有提到) - 而且我提出这个问题对我来说有点问题,但在这里(它是Linux的例子).

举一个正确的例子,首先我需要能够对CGI Web服务器进行非常简单的模拟的东西,最好是通过一个命令行.找到简单命令行Web服务器以运行cgis之后.(perlmonks.org),我发现IO :: All - 一个小型Web服务器适用于此测试.

在这里,我将在/tmp目录中工作; CGI脚本将/tmp/test.pl(包括在下面).请注意,IO::All服务器仅提供与CGI在同一目录中的可执行文件,因此chmod +x test.pl在此处是必需的.因此,要进行通常的CGI测试运行,我将目录更改/tmp为终端,并在那里运行单行Web服务器:

$ cd /tmp
$ perl -MIO::All -e 'io(":8080")->fork->accept->(sub { $_[0] < io(-x $1 ? "./$1 |" : $1) if /^GET \/(.*) / })'
Run Code Online (Sandbox Code Playgroud)

webserver命令将在终端中阻塞,否则将在本地启动Web服务器(在127.0.0.1或localhost) - 之后,我可以转到Web浏览器,并请求此地址:

http://127.0.0.1:8080/test.pl
Run Code Online (Sandbox Code Playgroud)

......我应该在Web浏览器中观察加载 - 并显示 - print所做的test.pl.


现在,要使用调试此脚本RemotePort,首先我们需要一个网络上的监听器,我们将通过它与Perl调试器进行交互; 我们可以使用命令行工具netcat(nc在这里看到:Perl如何远程调试?).因此,首先netcat在一个终端中运行侦听器 - 它将阻塞并等待端口7234上的连接(这将是我们的调试端口):

$ nc -l 7234
Run Code Online (Sandbox Code Playgroud)

然后,我们想要perl在调试模式下启动RemotePort,当test.pl调用时(即使在CGI模式下,通过服务器).在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
Run Code Online (Sandbox Code Playgroud)

这有点棘手 - 请参阅shell脚本 - 如何在shebang中使用环境变量?- Unix和Linux堆栈交换.但是,这里的诀窍似乎不是分叉perl处理的解释器test.pl - 所以一旦我们点击它,我们不会exec,但我们称之为perl"明白",并且基本上" test.pl使用"我们的脚本使用do(请参阅如何运行Perl脚本中的Perl脚本?).

现在我们已经perldbgcall.sh进入/tmp- 我们可以更改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";
Run Code Online (Sandbox Code Playgroud)

现在,他们test.pl和新的shebang经理perldbgcall.sh都在/tmp; 我们已经nc在端口7234上监听调试连接 - 所以我们最终可以打开另一个终端窗口,将目录更改为/tmp,并运行单行Web服务器(将在端口8080上侦听Web连接):

cd /tmp
perl -MIO::All -e 'io(":8080")->fork->accept->(sub { $_[0] < io(-x $1 ? "./$1 |" : $1) if /^GET \/(.*) / })'
Run Code Online (Sandbox Code Playgroud)

完成此操作后,我们可以转到我们的Web浏览器,并请求相同的地址http://127.0.0.1:8080/test.pl.但是,现在当web服务器尝试执行脚本时,它将通过perldbgcall.shshebang 执行此操作- 它将perl以远程调试器模式启动.因此,脚本执行将暂停 - 因此Web浏览器将锁定,等待数据.我们现在可以切换到netcat终端,我们应该看到熟悉的Perl调试器文本 - 但是,通过nc以下方式输出:

$ nc -l 7234

Loading DB routines from perl5db.pl version 1.32
Editor support available.

Enter h or `h h' 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>
Run Code Online (Sandbox Code Playgroud)

正如代码片段所示,我们现在基本上nc用作"终端" - 所以我们可以键入r(和Enter)进行"运行" - 脚本将运行断点语句(参见Perl,$之间有什么区别) DB :: single = 1和2?),再次停止之前(注意,此时浏览器仍会锁定).

所以,现在我们可以test.pl通过nc终端逐步完成剩下的工作:

....
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,
  h q, h R or h o to get additional info.
  DB<1>
Run Code Online (Sandbox Code Playgroud)

...但是,此时,浏览器还会锁定并等待数据.只有在我们退出调试器后q:

  DB<1> q
$
Run Code Online (Sandbox Code Playgroud)

...浏览器是否会停止锁定 - 最后显示以下内容的(完整)输出test.pl:

YEAH hello 2 there CMON
CMON hello 3 there YEAH
STEP hello 4 there NOW
STEP hello 5 there AGAIN
Run Code Online (Sandbox Code Playgroud)

当然,即使不运行Web服务器也可以进行这种调试 - 但是,这里的巧妙之处在于我们根本不接触Web服务器; 我们从Web浏览器"本机地"(对于CGI)触发执行 - 并且CGI脚本本身所需的唯一更改是shebang的更改(当然,shebang包装脚本的存在,作为可执行文件在同一个中)目录).

嗯,希望这有助于某人 - 我肯定会喜欢偶然发现这一点,而不是自己写:)
干杯!


zaw*_*tut 5

对我来说,我使用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");
Run Code Online (Sandbox Code Playgroud)