如何排除Perl CGI脚本的故障?

34gzjxbg  于 2022-12-19  发布在  Perl
关注(0)|答案(8)|浏览(175)

我有一个Perl CGI脚本不工作,我不知道如何开始缩小问题的范围。我该怎么办?
注意:我添加这个问题是因为我真的想添加我对堆栈溢出的冗长回答。我在其他回答中一直有外部链接到它,它应该在这里。如果你有什么要添加的,不要羞于编辑我的回答。

46scxncf

46scxncf1#

这个答案旨在作为解决Perl CGI脚本问题的通用框架,最初在Perlmonks上以Troubleshooting Perl CGI Scripts的形式出现。它不是您可能遇到的每个问题的完整指南,也不是关于bug压制的教程。它只是我调试CGI脚本二十年经验的总结(加!)年。这个页面似乎有许多不同的家园,我似乎忘记了它的存在,所以我把它添加到StackOverflow。你可以发送任何意见或建议给我在bdfoy@cpan.org。它也是社区维基,但是不要太疯狂。:)

您是否使用Perl的内置特性来帮助您查找问题?

打开警告功能,让Perl警告您代码中有问题的部分。您可以在命令行中使用-w开关,这样您就不必更改任何代码或向每个文件添加杂注:

% perl -w program.pl

但是,您应该强迫自己总是通过向所有文件添加warnings杂注来清除有问题的代码:

use warnings;

如果您需要除简短警告消息之外的更多信息,请使用diagnostics杂注获取更多信息,或查看perldiag文档:

use diagnostics;

是否先输出了有效的CGI头?

服务器希望CGI脚本的第一个输出是CGI标头。通常,它可能简单到print "Content-type: text/plain\n\n";CGI.pm及其派生项print header()。某些服务器对在标准输出(STDOUT)之前显示错误输出(STDERR)很敏感。

尝试向浏览器发送错误

添加此行

use CGI::Carp 'fatalsToBrowser';

添加到脚本中。这还会向浏览器窗口发送编译错误。请确保在移动到生产环境之前删除此错误,因为额外的信息可能会带来安全风险。

错误日志显示了什么内容?

服务器保存错误日志(或者至少应该保存)。服务器和脚本的错误输出应该显示在那里。找到错误日志,看看它说了什么。日志文件没有标准的位置。在服务器配置中查找它们的位置,或者询问服务器管理员。你也可以使用CGI::Carp之类的工具来保存你自己的日志文件。

脚本的权限是什么?

如果您看到“Permission denied”或“Method not implemented”之类的错误,这可能意味着您的脚本无法被Web服务器用户读取和执行。在Unix版本中,建议将模式更改为755:chmod 755 filename。切勿将模式设置为777!

您使用的是use strict吗?

请记住,Perl在你第一次使用变量时会自动创建变量。这是一个特性,但有时如果你输入错误的变量名会导致错误。pragma use strict会帮助你发现这类错误。在你习惯之前,这会很烦人,但一段时间后你的编程会有很大的改进,你可以自由地犯不同的错误。

脚本是否编译?

您可以使用-c开关检查编译错误。集中注意报告的第一个错误。冲洗,重复。如果您遇到非常奇怪的错误,请检查以确保您的脚本具有正确的行尾。如果您以二进制模式FTP,从CVS checkout ,或其他不处理行尾转换的操作,网络服务器可能会把你的脚本看作一个大行。2以ASCII模式传输Perl脚本。

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

如果您的脚本抱怨不安全的依赖关系,您可能使用了-T开关来打开污点模式,这是一件好事,因为它可以让你通过未经检查的数据到 shell 。如果它抱怨,它正在做它的工作,以帮助我们编写更安全的脚本。任何数据来源于外部的程序(即环境)被认为是污染的。环境变量如PATHLD_LIBRARY_PATH特别麻烦。你必须将它们设置为一个安全的值或完全取消设置。正如我建议的那样。无论如何,你应该使用绝对路径。如果污点检查抱怨其他的东西,确保你没有污染数据。详细信息请参见perlsec手册页。

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

当从命令行运行时,脚本是否输出您期望的内容?是否首先输出标题,然后是空行?请记住,如果您在终端上,STDERR可能会与STDOUT合并(例如交互式会话),并且由于缓冲可能会以混乱的顺序出现。通过将$|设置为true值来打开Perl的自动刷新特性。通常你会在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服务器环境。如果出现问题,则存在环境问题。
取消设置或删除这些变量

  • PATH
  • x1米20英寸1x
  • 所有ORACLE_*变量

设置这些变量

  • 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这样的包得到同样的功能

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

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

脚本是否在您认为的位置?

脚本的文件系统路径不一定与脚本的URL路径直接相关。请确保目录正确,即使必须编写简短的测试脚本来进行测试也是如此。此外,是否确实修改了正确的文件?如果更改没有任何效果,则可能是修改了其他文件,或者将文件上载到了错误的位置。(顺便说一句,这是我最经常引起这种麻烦的原因;)

您使用的是CGI.pm还是它的衍生产品?

如果您的问题与解析CGI输入有关,并且您没有使用CGI.pmCGI::RequestCGI::SimpleCGI::Lite等经过广泛测试的模块,请使用这些模块并继续使用。CGI.pm具有cgi-lib.pl兼容模式,可以帮助您解决由于旧CGI解析器实现而导致的输入问题。

是否使用绝对路径?

如果你正在运行带有system、反勾号或其他IPC工具的外部命令,你应该使用外部程序的绝对路径。你不仅知道你正在运行什么,而且还可以避免一些安全问题。如果你正在打开文件进行阅读或写,使用绝对路径。CGI脚本可能与您对当前目录有不同的想法。或者,您可以执行显式chdir()来将您置于正确的位置。

是否检查了返回值?

大多数Perl函数会告诉你它们是否工作,并在失败时设置$!。你检查了返回值并检查了$!的错误消息吗?如果你使用eval,你检查了$@吗?

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

Perl的最新稳定版本是5.28(或不是,取决于最后一次编辑的时间)。您使用的是旧版本吗?不同版本的Perl可能有不同的警告。

您正在使用哪个Web服务器?

不同的服务器在相同的情况下可能会有不同的行为。相同的服务器产品在不同的配置下可能会有不同的行为。请在任何帮助请求中尽可能多地提供此信息。

是否查看了服务器文档?

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

您搜索过comp.infosystems.www.authoring.cgi的归档文件吗?

  • 这曾经是有用的,但所有的好海报要么死了,要么走了。*

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

是否可以使用简短的测试脚本重现该问题?

在大型系统中,由于发生的事情太多,要追踪一个bug可能会很困难。尝试用尽可能短的脚本重现问题行为。知道问题是修复的主要部分。这当然会很耗时,但你还没有找到问题,而且你已经没有选择了。:)
你决定去看电影了吗?

说真的。有时候我们会太过专注于问题,以至于产生了“感知狭窄”(隧道视野)。休息一下,喝杯咖啡,或者在《杜克》、《雷神之锤》、《末日》、《光晕》、《COD》中干掉一些坏人,可能会给予你重新解决问题所需的新鲜视角。

你说出问题了吗?

说真的。有时候大声解释问题会让我们找到自己的答案。和企鹅(毛绒玩具)说话,因为你的同事没有在听。如果你对这个调试工具感兴趣(如果你现在还没有发现问题,我推荐你使用它),你可能也想读一下The Psychology of Computer Programming

3yhwsihp

3yhwsihp2#

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

nhhxz33t

nhhxz33t3#

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

die语句和其他致命的运行时和编译时错误会打印到STDERR,这些错误可能很难找到,并且可能会与站点中其他网页的消息混在一起。在调试脚本时,最好以某种方式在浏览器中显示致命错误消息。
一种方法是调用

use CGI::Carp qw(fatalsToBrowser);

在脚本的顶部。该调用将安装一个$SIG{__DIE__}处理程序(参见perlvar),在浏览器中显示致命错误,如果需要的话,在它前面加上一个有效的头。在我听说CGI::Carp之前,我使用的另一个CGI调试技巧是在脚本上使用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

这种更详细的技术比CGI::Carp稍有优势,因为它将捕获更多的编译时错误。

**更新:**我从来没有使用过它,但正如Mikael S建议的那样,CGI::Debug似乎也是一个非常有用的、可配置的工具。

kqqjbcuj

kqqjbcuj4#

我想知道为什么没有人提到PERLDB_OPTS选项RemotePort;尽管无可否认,Web上没有太多可用的示例(RemotePort甚至没有在perldebug中提到)--而且我提出这个示例有点问题,但它就在这里(它是一个Linux示例)。
为了做一个合适的示例,首先我需要一个可以对CGI Web服务器进行非常简单的模拟的东西,最好是通过一个命令行。在找到Simple command line web server for running cgis. (perlmonks.org)之后,我发现IO::All-A Tiny Web Server适用于此测试。
在这里,我将在/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 \/(.*) / })'

The webserver command will block in the terminal, and will otherwise start the web server locally (on 127.0.0.1 or localhost ) - afterwards, I can go to a web browser, and request this address:

http://127.0.0.1:8080/test.pl

......我应该观察到test.pl制作的print正在Web浏览器中加载-并显示。
现在,要用RemotePort调试这个脚本,首先我们需要在网络上有一个 * 侦听器 *,通过它我们将与Perl调试器交互;我们可以使用命令行工具netcatnc,见此处:因此,首先在一个终端中运行netcat侦听器-它将阻塞并等待端口7234(将是我们的调试端口)上的连接:

$ nc -l 7234

然后,当test.pl被调用时(即使是在CGI模式下,通过服务器),我们希望perl在调试模式下以RemotePort启动。在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 script - How can I use environment variables in my shebang? - Unix & Linux Stack Exchange。但是,这里的技巧似乎是 * 不 * 派生处理test.plperl解释器--所以一旦我们遇到它,我们就不使用exec,而是"简单地"调用perl,并且基本上使用do来"源"我们的test.pl脚本(参见How do I run a Perl script from within a Perl script?)。
现在我们在/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上的调试连接-所以我们最终可以打开另一个终端窗口,将目录更改为/tmp,并在那里运行一行程序webserver(它将监听端口8080上的web连接):

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

完成后,我们可以转到Web浏览器,并请求相同的地址http://127.0.0.1:8080/test.pl。但是,现在当Web服务器尝试执行脚本时,它将通过perldbgcall.sh shebang执行,这将以远程调试器模式启动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>

如代码片段所示,我们现在基本上使用nc作为"终端"-因此我们可以键入r(和Enter)表示"运行"-脚本将运行断点语句(另请参见In perl, what is the difference between $DB::single = 1 and 2?),然后再次停止(注意,此时浏览器仍将锁定)。
那么,现在我们可以通过nc终端逐步执行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,
  h q, h R or h o to get additional info.
  DB<1>

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

DB<1> q
$

...浏览器是否停止锁定-并最终显示test.pl的(完整)输出:

YEAH hello 2 there CMON
CMON hello 3 there YEAH
STEP hello 4 there NOW
STEP hello 5 there AGAIN

当然,这种调试甚至可以在不运行web服务器的情况下完成--然而,这里的巧妙之处在于,我们根本不接触web服务器;我们从网络浏览器"本地"触发执行(对于CGI)-CGI脚本本身唯一需要的改变是shebang的改变(当然,shebang Package 器脚本的存在,作为同一目录中的可执行文件)。
好吧,希望这对某些人有帮助-我当然很想偶然发现这一点,而不是自己写:)
干杯!

tktrz96b

tktrz96b5#

对我来说,我使用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");
sgtfey8w

sgtfey8w6#

老实说,你可以做这篇文章上面所有有趣的事情。尽管如此,我找到的最简单、最积极主动的解决方案就是“打印出来”。
例如:(正常代码)

`$somecommand`;

看看它是否在做我真正想让它做的事情:(故障排除)

print "$somecommand";
zfciruhq

zfciruhq7#

可能还值得一提的是,当您从命令行执行Perl脚本时,Perl总是会告诉您错误发生在哪一行(例如SSH会话)。
如果其他方法都失败了,我通常会这样做。我将SSH连接到服务器,然后手动执行Perl脚本。例如:

% perl myscript.cgi

如果有问题,Perl会告诉你,这种调试方法可以解决任何文件权限相关的问题,或者Web浏览器或Web服务器的问题。

mcdcgff0

mcdcgff08#

您可以使用以下命令在终端中运行perl cgi-script

$ perl filename.cgi

它解释代码并以HTML代码提供结果。如果有错误,它会报告。

相关问题