首页 文章

如何解决Perl CGI脚本问题?

提问于
浏览
96

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


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

8 回答

  • 8

    我想知道为什么没有人提到名为 RemotePortPERLDB_OPTS 选项;虽然不可否认,网上的工作实例并不多( RemotePortperldebug中甚至没有提及) - 而且我提出这个问题有点问题,但在这里(它是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 \/(.*) / })'
    

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

    http://127.0.0.1:8080/test.pl
    

    ......我应该观察 print 在网页浏览器中加载并显示的 print .


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

    $ nc -l 7234
    

    然后,当调用 test.pl 时(即使在CGI模式下,通过服务器),我们希望 perlRemotePort 启动调试模式 . 在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 "plainly",基本上"source"我们的 test.pl 脚本使用 do (见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 ,然后运行一线网络服务器(它将侦听端口8080上的网络连接):

    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 作为"terminal" - 所以我们可以为"run"键入 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服务器;我们从Web浏览器“本机地”触发执行(对于CGI) - 并且CGI脚本本身所需的唯一更改是shebang的更改(当然,shebang包装脚本的存在,作为同一个可执行文件)目录) .

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

  • 1

    老实说,你可以在这篇文章上面做所有有趣的事情 . 尽管如此,我发现最简单,最主动的解决方案就是“打印” .

    例如:(普通代码)

    `$somecommand`;
    

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

    print "$somecommand";
    
  • 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");
    
  • 1

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

  • 123
    • Are you using an error handler while you are debugging?

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

    一种方法是打电话

    use CGI::Carp qw(fatalsToBrowser);
    

    在脚本的顶部 . That call将在浏览器中安装 $SIG{__DIE__} 处理程序(请参阅perlvar)显示致命错误,必要时在其前面加上有效标头 . 在我听说 CGI::Carp 之前我使用的另一个CGI调试技巧是使用 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 略有优势,因为它会捕获更多的编译时错误 .

    Update: 我从来没有使用过它,但看起来像Mikael S建议的那样CGI::Debug也是一个非常有用且可配置的工具 .

  • 10

    这个答案旨在作为解决Perl CGI脚本问题的一般框架,最初出现在Perlmonks上为Troubleshooting Perl CGI Scripts . 它不是您可能遇到的每个问题的完整指南,也不是关于破坏bug的教程 . 这是我调试CGI脚本二十年(加!)年的经验的高潮 . 这个页面似乎有许多不同的家,我似乎忘了它存在,所以我也是社区维基,但不要太疯狂 . :)


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

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

    % perl -w program.pl
    

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

    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';
    

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

    错误日志说了什么?

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

    脚本的权限是什么?

    如果您看到"Permission denied"或"Method not implemented"等错误,则可能表示您的脚本不可由Web服务器用户读取和执行 . 在Unix的风格上,建议将模式更改为755: chmod 755 filename . 永远不要将模式设置为777!

    你使用严格吗?

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

    脚本是否编译?

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

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

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

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

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

    $|++;                            #sets $| for STDOUT
    $old_handle = select( STDERR );  #change to STDERR
    $|++;                            #sets $| for STDERR
    select( $old_handle );           #change back to STDOUT
    

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

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

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

    取消设置或删除这些变量

    • PATH

    • LD_LIBRARY_PATH

    • 所有 ORACLE_* 变量

    设置这些变量

    • REQUEST_METHOD (根据需要设置为 GETHEADPOST

    • SERVER_PORT (通常设为80)

    • REMOTE_USER (如果您正在进行受保护的访问)

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

    use CGI qw(-debug)
    

    你在使用die()还是警告?

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

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

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

    脚本是您认为的吗?

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

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

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

    你使用绝对路径了吗?

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

    您检查了您的退货 Value 吗?

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

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

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

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

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

    您检查过服务器文档吗?

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

    您是否搜索过comp.infosystems.www.authoring.cgi的档案?

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

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

    您可以使用简短的测试脚本重现问题吗?

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

    你决定去看电影吗?

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

    你有问题吗?

    再说一遍 . 有时大声解释问题会引导我们找到自己的答案 . 与企鹅(毛绒玩具)交谈,因为你的同事现在还没有发现问题,你可能也想阅读The Psychology of Computer Programming .

  • 7

    可能还值得一提的是,当您从命令行执行Perl脚本时,Perl将始终告诉您错误发生在哪一行 . (例如SSH会话)

    如果一切都失败了,我通常会这样做 . 我将SSH进入服务器并手动执行Perl脚本 . 例如:

    % perl myscript.cgi
    

    如果有问题,那么Perl会告诉你它 . 此调试方法消除了与文件权限相关的任何问题或Web浏览器或Web服务器问题 .

  • 0

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

    $ perl filename.cgi
    

    它解释代码并使用HTML代码提供结果 . 如果有的话,它将报告错误 .

相关问题