Como posso solucionar o problema do meu script Perl CGI?

100

Eu tenho um script Perl que não está funcionando e não sei como começar a restringir o problema. O que eu posso fazer?


Observação: estou adicionando a pergunta porque realmente quero adicionar minha resposta muito longa ao Stackoverflow. Continuo ligando externamente para ele em outras respostas e ele merece estar aqui. Não tenha vergonha de editar minha resposta se você tiver algo a acrescentar.

brian d foy
fonte
5
@Evan - meu ponto é simplesmente que mesmo não sendo CW, ainda é editável - se você tiver carma suficiente; 100 para CW, 2k caso contrário. Então, agora você tem 2060, você deve ser capaz de editar postagens não CW.
Marc Gravell
1
@Evan, os pontos mágicos estão listados nas dicas de ferramentas na coluna da direita aqui: stackoverflow.com/privileges
cjm
Se o seu navegador estiver exibindo ruído de linha, ele pode estar imprimindo o script perl. Nesse caso, consulte stackoverflow.com/questions/2621161/…
Andrew Grimm

Respostas:

129

Esta resposta pretende ser uma estrutura geral para resolver problemas com scripts Perl CGI e apareceu originalmente no Perlmonks como Troubleshooting Perl CGI Scripts . Não é um guia completo para todos os problemas que você pode encontrar, nem um tutorial sobre como eliminar bugs. É apenas o culminar da minha experiência depurando scripts CGI por vinte (mais!) Anos. Esta página parece ter muitas casas diferentes e parece que esqueci que ela existe, então estou adicionando-a ao StackOverflow. Você pode enviar qualquer comentário ou sugestão para mim em [email protected]. Também é wiki da comunidade, mas não enlouqueça. :)


Você está usando os recursos integrados do Perl para ajudá-lo a encontrar problemas?

Ative os avisos para permitir que o Perl o avise sobre partes questionáveis ​​do seu código. Você pode fazer isso a partir da linha de comando com a -wopção para que não precise alterar nenhum código ou adicionar um pragma a cada arquivo:

 % perl -w program.pl

No entanto, você deve se forçar a sempre esclarecer o código questionável, adicionando o warningspragma a todos os seus arquivos:

 use warnings;

Se precisar de mais informações do que a curta mensagem de aviso, use o diagnosticspragma para obter mais informações ou consulte a documentação do perldiag :

 use diagnostics;

Você gerou um cabeçalho CGI válido primeiro?

O servidor espera que a primeira saída de um script CGI seja o cabeçalho CGI. Normalmente, isso pode ser tão simples quanto print "Content-type: text/plain\n\n";ou com CGI.pm e seus derivados print header(),. Alguns servidores são sensíveis à saída de erro (ativada STDERR) exibida antes da saída padrão (ativada STDOUT).

Tente enviar erros para o navegador

Adicione esta linha

 use CGI::Carp 'fatalsToBrowser';

ao seu script. Isso também envia erros de compilação para a janela do navegador. Certifique-se de removê-lo antes de mudar para um ambiente de produção, pois as informações extras podem representar um risco à segurança.

O que o log de erros diz?

Os servidores mantêm logs de erros (ou deveriam, pelo menos). A saída de erro do servidor e do seu script deve aparecer lá. Encontre o log de erros e veja o que ele diz. Não existe um local padrão para arquivos de log. Procure sua localização na configuração do servidor ou pergunte ao administrador do servidor. Você também pode usar ferramentas como CGI :: Carp para manter seus próprios arquivos de log.

Quais são as permissões do script?

Se você vir erros como "Permissão negada" ou "Método não implementado", provavelmente significa que seu script não pode ser lido e executado pelo usuário do servidor da web. Em sabores de Unix, é recomendado alterar o modo para 755: chmod 755 filename. Nunca defina um modo para 777!

Você está usando use strict?

Lembre-se de que Perl cria variáveis ​​automaticamente quando você as usa pela primeira vez. Este é um recurso, mas às vezes pode causar erros se você digitar incorretamente o nome de uma variável. O pragma use stricto ajudará a encontrar esses tipos de erros. É irritante até você se acostumar, mas sua programação irá melhorar significativamente depois de um tempo e você estará livre para cometer diversos erros.

O script compila?

Você pode verificar se há erros de compilação usando a -c opção. Concentre-se nos primeiros erros relatados. Enxágüe, repita. Se você estiver recebendo erros realmente estranhos, verifique se o seu script tem os finais de linha corretos. Se você FTP em modo binário, check-out do CVS ou qualquer outra coisa que não lide com tradução de fim de linha, o servidor da web pode ver seu script como uma grande linha. Transfira scripts Perl no modo ASCII.

O script está reclamando de dependências inseguras?

Se o seu script reclamar de dependências inseguras, provavelmente você está usando a -Topção para ativar o modo contaminado, o que é uma coisa boa, pois mantém você passando dados não verificados para o shell. Se estiver reclamando, está fazendo seu trabalho para nos ajudar a escrever scripts mais seguros. Quaisquer dados provenientes de fora do programa (ou seja, o meio ambiente) são considerados contaminados. Variáveis ​​de ambiente como PATHe LD_LIBRARY_PATH são particularmente problemáticas. Você deve defini-los com um valor seguro ou removê-los completamente, como eu recomendo. Você deve usar caminhos absolutos de qualquer maneira. Se a verificação de contaminação reclamar de outra coisa, certifique-se de que os dados não foram corrompidos. Veja a página de manual do perlsec para detalhes.

O que acontece quando você o executa a partir da linha de comando?

O script produz o que você espera ao ser executado na linha de comando? A saída do cabeçalho é primeiro, seguida por uma linha em branco? Lembre-se de que STDERRpode ser mesclado com STDOUT se você estiver em um terminal (por exemplo, uma sessão interativa) e, devido ao buffering, pode aparecer em uma ordem confusa. Ative o recurso autoflush do Perl definindo $|um valor verdadeiro. Normalmente você pode ver $|++;em programas CGI. Depois de definidas, todas as impressões e gravações irão imediatamente para a saída em vez de serem armazenadas em buffer. Você tem que definir isso para cada filehandle. Use selectpara alterar o manipulador de arquivos padrão, como:

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

De qualquer forma, a primeira saída deve ser o cabeçalho CGI seguido por uma linha em branco.

O que acontece quando você o executa a partir da linha de comando com um ambiente semelhante a CGI?

O ambiente do servidor da web é geralmente muito mais limitado do que o ambiente da linha de comando e possui informações extras sobre a solicitação. Se o seu script funcionar bem na linha de comando, você pode tentar simular um ambiente de servidor da web. Se o problema aparecer, você tem um problema de ambiente.

Cancele ou remova essas variáveis

  • PATH
  • LD_LIBRARY_PATH
  • todas as ORACLE_*variáveis

Defina essas variáveis

  • REQUEST_METHOD(definida como GET, HEADou POSTcomo apropriado)
  • SERVER_PORT (definido para 80, normalmente)
  • REMOTE_USER (se você estiver fazendo coisas de acesso protegido)

Versões recentes de CGI.pm(> 2.75) requerem o -debugsinalizador para obter o comportamento antigo (útil), portanto, pode ser necessário adicioná-lo às suas CGI.pmimportações.

use CGI qw(-debug)

Você está usando die()ou warn?

Essas funções são impressas, a STDERRmenos que você as tenha redefinido. Eles também não geram um cabeçalho CGI. Você pode obter a mesma funcionalidade com pacotes como CGI :: Carp

O que acontece depois que você limpa o cache do navegador?

Se você acha que seu script está fazendo a coisa certa e, ao executar a solicitação manualmente, obtém a saída correta, o navegador pode ser o culpado. Limpe o cache e defina o tamanho do cache para zero durante o teste. Lembre-se de que alguns navegadores são realmente estúpidos e não recarregam conteúdo novo, mesmo que você diga para fazer isso. Isso é especialmente prevalente nos casos em que o caminho da URL é o mesmo, mas o conteúdo muda (por exemplo, imagens dinâmicas).

O script está onde você pensa que está?

O caminho do sistema de arquivos para um script não está necessariamente relacionado ao caminho da URL para o script. Certifique-se de ter o diretório correto, mesmo se você tiver que escrever um pequeno script de teste para testar isso. Além disso, tem certeza de que está modificando o arquivo correto? Se você não notar nenhum efeito nas alterações, pode estar modificando um arquivo diferente ou enviando um arquivo para o local errado. (Esta é, a propósito, minha causa mais frequente de tais problemas;)

Você está usando CGI.pm, ou um derivado dele?

Se o seu problema está relacionado a analisar a entrada CGI e você não estiver usando um módulo amplamente testado como CGI.pm, CGI::Request, CGI::Simpleou CGI::Lite, utilize o módulo e seguir com a vida. CGI.pmtem um cgi-lib.plmodo de compatibilidade que pode ajudá-lo a resolver problemas de entrada devido a implementações de analisador CGI mais antigas.

Você usou caminhos absolutos?

Se você estiver executando comandos externos com system, back ticks ou outros recursos IPC, você deve usar um caminho absoluto para o programa externo. Você não apenas sabe exatamente o que está executando, mas também evita alguns problemas de segurança. Se você estiver abrindo arquivos para leitura ou gravação, use um caminho absoluto. O script CGI pode ter uma ideia diferente sobre o diretório atual da sua. Alternativamente, você pode fazer um explícito chdir()para colocá-lo no lugar certo.

Você verificou seus valores de retorno?

A maioria das funções Perl dirá se elas funcionaram ou não e serão configuradas $!em caso de falha. Você verificou o valor de retorno e examinou as $!mensagens de erro? Você verificou $@se estava usando eval?

Qual versão do Perl você está usando?

A última versão estável do Perl é 5.28 (ou não, dependendo de quando foi editado pela última vez). Você está usando uma versão mais antiga? Versões diferentes do Perl podem ter idéias diferentes de avisos.

Qual servidor web você está usando?

Servidores diferentes podem agir de maneira diferente na mesma situação. O mesmo produto de servidor pode agir de maneira diferente com configurações diferentes. Inclua o máximo dessas informações que puder em qualquer pedido de ajuda.

Você verificou a documentação do servidor?

Os programadores CGI sérios devem saber o máximo possível sobre o servidor - incluindo não apenas os recursos e o comportamento do servidor, mas também a configuração local. A documentação do seu servidor pode não estar disponível se você estiver usando um produto comercial. Caso contrário, a documentação deve estar em seu servidor. Se não for, procure na web.

Você pesquisou os arquivos de comp.infosystems.www.authoring.cgi?

Este uso é útil, mas todos os bons cartazes morreram ou se perderam.

É provável que alguém já tenha tido o seu problema antes e que alguém (possivelmente eu) o tenha respondido neste newsgroup. Embora este newsgroup tenha passado do seu apogeu, a sabedoria coletada no passado às vezes pode ser útil.

Você pode reproduzir o problema com um pequeno script de teste?

Em sistemas grandes, pode ser difícil rastrear um bug, pois muitas coisas estão acontecendo. Tente reproduzir o comportamento do problema com o script mais curto possível. Saber o problema é a maior parte da correção. Certamente, isso pode ser demorado, mas você ainda não encontrou o problema e está ficando sem opções. :)

Você decidiu ir ver um filme?

Seriamente. Às vezes, podemos ficar tão envolvidos com o problema que desenvolvemos um "estreitamento perceptivo" (visão de túnel). Fazer uma pausa, tomar uma xícara de café ou atacar alguns bandidos em [Duke Nukem, Quake, Doom, Halo, COD] pode lhe dar uma nova perspectiva de que você precisa para abordar novamente o problema.

Você vocalizou o problema?

Sério de novo. Às vezes, explicar o problema em voz alta nos leva às nossas próprias respostas. Fale com o pinguim (brinquedo de pelúcia) porque seus colegas de trabalho não estão ouvindo. Se você estiver interessado nisso como uma ferramenta de depuração séria (e eu a recomendo, se você ainda não encontrou o problema), você também pode ler The Psychology of Computer Programming .

brian d foy
fonte
4
Não tenha vergonha de editar minha resposta se você tiver algo a acrescentar.
brian d foy
Parece que você pode querer remover o link para o CGI meta FAQ. 5.12.1 é considerado "estável"?
Snake Plissken
1
Por que não em $|=1vez de $|++?
reinierpost
Por que em $|=1vez de $|++? Realmente não faz diferença e, mesmo assim, $|é mágico.
brian d foy
2
Boa resposta, acho que vale a pena mencionar que algumas dessas soluções devem ser puramente para solução de problemas e não colocadas em código de produção. use strictgeralmente é bom usar sempre, enquanto o uso fatalsToBrowserpode não ser aconselhado na produção, especialmente se você estiver usando die.
Vol7ron
10

Acho que CGI :: Debug também vale a pena mencionar.

Mikael S
fonte
Infelizmente, só posso editar a pergunta, não as respostas.
Mikael S
7

Você está usando um gerenciador de erros durante a depuração?

diedeclarações e outros erros fatais de tempo de execução e de compilação são impressos STDERR, o que pode ser difícil de encontrar e pode ser confundido com mensagens de outras páginas da web em seu site. Enquanto você estiver depurando seu script, é uma boa ideia fazer com que as mensagens de erro fatais sejam exibidas em seu navegador de alguma forma.

Uma maneira de fazer isso é ligar

   use CGI::Carp qw(fatalsToBrowser);

no topo do seu script. Essa chamada instalará um $SIG{__DIE__}manipulador (consulte perlvar ) para exibir erros fatais em seu navegador, acrescentando um cabeçalho válido se necessário. Outro truque de depuração CGI que usei antes de ouvir falar CGI::Carpera usar evalcom os recursos DATAe __END__no script para detectar erros em tempo de compilação:

   #!/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

Essa técnica mais detalhada tem uma pequena vantagem, CGI::Carppois captura mais erros de tempo de compilação.

Update: Nunca usei, mas parece que CGI::Debug, como sugeriu Mikael S, também é uma ferramenta muito útil e configurável para esse fim.

mob
fonte
3
@Ether: <DATA>é um identificador de arquivo mágico que lê o script atual começando com __END__. Join está fornecendo um contexto de lista, então <fh> retorna uma matriz, uma linha por item. Em seguida, o join o reúne (unindo-o com ''). Finalmente, eval.
derobert
@Ether: Uma maneira mais legível de escrever a linha 2 seria:eval join(q{}, <DATA>);
derobert 09/09/10
@derobert: na verdade, __DATA__ é o token usado para iniciar a seção de dados, não __END__ (acho que foi essa a minha confusão).
Ether,
1
@Ether: Bem, na verdade, ambos funcionam no script de nível superior (de acordo com a página de manual do perldata). Mas como DATA é o preferido, mudei a resposta.
derobert de
@derobert: obrigado pelo link do doc; Eu não sabia sobre o comportamento de compatibilidade com versões anteriores de __END__!
Ether
7

Eu me pergunto como ninguém mencionou a PERLDB_OPTSopção chamada RemotePort; embora reconheço que não há muitos exemplos de trabalho na web ( RemotePortnem mesmo é mencionado em perldebug ) - e foi meio problemático para mim inventar este, mas aqui vai (sendo um exemplo do Linux).

Para fazer um exemplo adequado, primeiro eu precisava de algo que pudesse fazer uma simulação muito simples de um servidor web CGI, de preferência por meio de uma única linha de comando. Depois de encontrar o servidor da web de linha de comando simples para executar cgis. (perlmonks.org) , descobri que o IO :: All - A Tiny Web Server é aplicável para este teste.

Aqui, vou trabalhar no /tmpdiretório; o script CGI será /tmp/test.pl(incluído abaixo). Observe que o IO::Allservidor servirá apenas arquivos executáveis ​​no mesmo diretório do CGI, portanto, chmod +x test.plé necessário aqui. Portanto, para fazer o teste CGI normal, mudo o diretório para /tmpno terminal e executo o servidor da Web de uma linha lá:

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

O comando do servidor da web será bloqueado no terminal e, caso contrário, iniciará o servidor da web localmente (em 127.0.0.1 ou localhost) - depois, posso ir para um navegador da web e solicitar este endereço:

http://127.0.0.1:8080/test.pl

... e devo observar o prints feito ao test.plser carregado - e mostrado - no navegador da web.


Agora, para depurar esse script RemotePort, primeiro precisamos de um listener na rede, por meio do qual interagiremos com o depurador Perl; podemos usar a ferramenta de linha de comando netcat( nc, viu isso aqui: Perl 如何 remote debug? ). Portanto, primeiro execute o netcatlistener em um terminal - onde bloqueará e aguardará as conexões na porta 7234 (que será nossa porta de depuração):

$ nc -l 7234

Então, queremos perlcomeçar no modo de depuração com RemotePort, quando o test.plfor chamado (mesmo no modo CGI, através do servidor). Isso, no Linux, pode ser feito usando o seguinte script "shebang wrapper" - que aqui também precisa estar em /tmpe deve ser tornado executável:

cd /tmp

cat > perldbgcall.sh <<'EOF'
#!/bin/bash
PERLDB_OPTS="RemotePort=localhost:7234" perl -d -e "do '$@'"
EOF

chmod +x perldbgcall.sh

Isso é uma coisa meio complicada - veja o script de shell - Como posso usar variáveis ​​de ambiente no meu shebang? - Unix e Linux Stack Exchange . Mas, o truque aqui parece ser não bifurcar o perlinterpretador que manipula test.pl - então, uma vez que o acertamos, não o fazemos exec, mas em vez disso, chamamos perl"claramente" e basicamente "fornecemos" nosso test.plscript usando do(consulte Como faço para executar um Script Perl de dentro de um script Perl? ).

Agora que temos perldbgcall.shem /tmp- nós podemos mudar o test.plarquivo, para que se refere a este arquivo executável em sua linha de shebang (em vez do intérprete habitual Perl) - aqui é /tmp/test.plmodificado assim:

#!./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";

Agora, ambos test.ple seu novo manipulador shebang,, perldbgcall.shestão na /tmp; e temos a ncescuta de conexões de depuração na porta 7234 - para que possamos finalmente abrir outra janela de terminal, alterar o diretório /tmpe executar o servidor da web one-liner (que escutará as conexões da web na porta 8080) lá:

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

Feito isso, podemos ir ao nosso navegador e solicitar o mesmo endereço http://127.0.0.1:8080/test.pl,. No entanto, agora, quando o servidor da web tentar executar o script, ele o fará por meio do perldbgcall.shshebang - que iniciará perlno modo de depuração remota. Assim, a execução do script será pausada - e o navegador da web travará, aguardando os dados. Agora podemos mudar para o netcatterminal e devemos ver o conhecido texto do depurador Perl - no entanto, a saída através de 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>

Como mostra o snippet, agora usamos basicamente nccomo um "terminal" - para que possamos digitar r(e Enter) para "executar" - e o script executará a instrução do ponto de interrupção (consulte também Em perl, qual é a diferença entre $ DB :: single = 1 e 2? ), Antes de parar novamente (observe que nesse ponto o navegador ainda travará).

Então, agora nós podemos, dizer, passo pelo resto do test.pl, através do ncterminal:

....
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>

... entretanto, também neste ponto, o navegador bloqueia e espera pelos dados. Somente depois de sairmos do depurador com q:

  DB<1> q
$

... o navegador para de bloquear - e finalmente exibe a saída (completa) de test.pl:

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

Claro, esse tipo de depuração pode ser feito mesmo sem executar o servidor web - no entanto, o legal aqui, é que não tocamos no servidor web; disparamos a execução "nativamente" (para CGI) a partir de um navegador da web - e a única mudança necessária no próprio script CGI é a mudança de shebang (e, claro, a presença do script shebang wrapper, como arquivo executável no mesmo diretório).

Bem, espero que isso ajude alguém - eu certamente adoraria ter tropeçado nisso, em vez de escrever eu mesmo :)
. Cheers!

sdaau
fonte
5

Para mim, eu uso log4perl . É muito útil e fácil.

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");
zawhtut
fonte
1

Honestamente, você pode fazer todas as coisas divertidas acima deste post. EMBORA, a solução mais simples e proativa que encontrei foi apenas "imprimir".

Por exemplo: (código normal)

`$somecommand`;

Para ver se ele está fazendo o que eu realmente quero: (Resolução de problemas)

print "$somecommand";
Ilan Kleiman
fonte
1

Provavelmente também valerá a pena mencionar que o Perl sempre dirá em qual linha o erro ocorre quando você executa o script Perl na linha de comando. (Uma sessão SSH, por exemplo)

Normalmente farei isso se tudo mais falhar. Vou usar o SSH no servidor e executar manualmente o script Perl. Por exemplo:

% perl myscript.cgi 

Se houver um problema, Perl irá informá-lo sobre ele. Este método de depuração elimina qualquer problema relacionado à permissão de arquivo ou navegador da web ou servidor da web.

gpowr
fonte
O Perl nem sempre informa o número da linha onde ocorre um erro. Ele informa o número da linha onde percebe que há algo errado. O erro provavelmente já aconteceu.
brian d foy
0

Você pode executar o perl cgi-script no terminal usando o comando abaixo

 $ perl filename.cgi

Ele interpreta o código e fornece o resultado com o código HTML. Ele relatará o erro se houver.

D.Karthikeyan
fonte
1
Desculpe, o comando $ perl -c filename.cgi valida a sintaxe do código e relata o erro, se houver. Ele não fornecerá o código html do cgi.
D.Karthikeyan
Invocar perl -c filenameirá de fato verificar apenas a sintaxe. Mas perl filenameimprime a saída HTML. Não há garantia de que não haverá um erro de 500 CGI, mas é um bom primeiro teste.
Nagev