Come posso risolvere i problemi del mio script CGI Perl?


100

Ho uno script Perl che non funziona e non so come iniziare a restringere il problema. Cosa posso fare?


Nota: sto aggiungendo la domanda perché voglio davvero aggiungere la mia risposta molto lunga a Stackoverflow. Continuo a collegarmi esternamente ad esso in altre risposte e merita di essere qui. Non essere timido nel modificare la mia risposta se hai qualcosa da aggiungere.


5
@Evan - il mio punto è semplicemente che, anche come non-CW ancora è modificabile - se avete abbastanza del karma; 100 per CW, 2k altrimenti. Quindi ora che hai 2060 dovresti essere in grado di modificare i post non CW.
Marc Gravell

1
@Evan, i punti magia sono elencati nelle descrizioni dei comandi nella colonna di destra qui: stackoverflow.com/privileges
CJM

Se il tuo browser web mostra il rumore di linea, potrebbe invece stampare lo script perl. In tal caso, vedere stackoverflow.com/questions/2621161/…
Andrew Grimm

Risposte:


129

Questa risposta è intesa come una struttura generale per risolvere i problemi con gli script CGI Perl ed è apparsa originariamente su Perlmonks come Risoluzione dei problemi degli script CGI Perl . Non è una guida completa a tutti i problemi che potresti incontrare, né un tutorial su come eliminare i bug. È solo il culmine della mia esperienza di debug di script CGI per venti (più!) Anni. Questa pagina sembra aver avuto molte case diverse e mi sembra di dimenticare che esiste, quindi la sto aggiungendo a StackOverflow. Puoi inviarmi commenti o suggerimenti a bdfoy@cpan.org. È anche un wiki della comunità, ma non impazzire. :)


Stai usando le funzionalità integrate di Perl per aiutarti a trovare i problemi?

Attiva gli avvisi per consentire a Perl di avvisarti delle parti discutibili del tuo codice. Puoi farlo dalla riga di comando con l' -winterruttore in modo da non dover modificare alcun codice o aggiungere un pragma a ogni file:

 % perl -w program.pl

Tuttavia, dovresti sforzarti di chiarire sempre il codice discutibile aggiungendo il warningspragma a tutti i tuoi file:

 use warnings;

Se hai bisogno di più informazioni rispetto al breve messaggio di avviso, usa il diagnosticspragma per ottenere maggiori informazioni o guarda nella documentazione di perldiag :

 use diagnostics;

Hai emesso prima un'intestazione CGI valida?

Il server si aspetta che il primo output di uno script CGI sia l'intestazione CGI. Tipicamente che potrebbe essere semplice come print "Content-type: text/plain\n\n";o con CGI.pm e suoi derivati, print header(). Alcuni server sono sensibili all'output di errore (attivato STDERR) visualizzato prima dell'output standard (attivato STDOUT).

Prova a inviare errori al browser

Aggiungi questa linea

 use CGI::Carp 'fatalsToBrowser';

al tuo script. Questo invia anche errori di compilazione alla finestra del browser. Assicurati di rimuoverlo prima di passare a un ambiente di produzione, poiché le informazioni aggiuntive possono rappresentare un rischio per la sicurezza.

Cosa diceva il registro degli errori?

I server conservano i log degli errori (o almeno dovrebbero). L'output di errore dal server e dal tuo script dovrebbe essere visualizzato lì. Trova il registro degli errori e guarda cosa dice. Non esiste una posizione standard per i file di registro. Cerca nella configurazione del server la loro posizione o chiedi all'amministratore del server. Puoi anche usare strumenti come CGI :: Carp per mantenere i tuoi file di registro.

Quali sono i permessi dello script?

Se vedi errori come "Autorizzazione negata" o "Metodo non implementato", probabilmente significa che il tuo script non è leggibile ed eseguibile dall'utente del server web. Su versioni di Unix, cambiando la modalità a 755 è raccomandato: chmod 755 filename. Non impostare mai una modalità su 777!

Stai usando use strict?

Ricorda che Perl crea automaticamente le variabili quando le usi per la prima volta. Questa è una caratteristica, ma a volte può causare bug se digiti erroneamente il nome di una variabile. Il pragma use strictti aiuterà a trovare questo tipo di errori. È fastidioso finché non ti ci abitui, ma la tua programmazione migliorerà notevolmente dopo un po 'e sarai libero di fare errori diversi.

Lo script si compila?

È possibile verificare la presenza di errori di compilazione utilizzando l' -c opzione. Concentrati sui primi errori segnalati. Risciacquare, ripetere. Se ricevi errori davvero strani, controlla che lo script abbia le terminazioni di riga corrette. Se utilizzi FTP in modalità binaria, esegui il checkout da CVS o qualcos'altro che non gestisce la traduzione di fine riga, il server web potrebbe vedere il tuo script come un'unica grande riga. Trasferisci script Perl in modalità ASCII.

Lo script si lamenta di dipendenze insicure?

Se il tuo script si lamenta di dipendenze non sicure, probabilmente stai usando l' -Tinterruttore per attivare la modalità di contaminazione, il che è una buona cosa poiché ti fa passare dati non controllati alla shell. Se si lamenta, sta facendo il suo lavoro per aiutarci a scrivere script più sicuri. Tutti i dati provenienti dall'esterno del programma (cioè l'ambiente) sono considerati contaminati. Le variabili ambientali come PATHe LD_LIBRARY_PATH sono particolarmente fastidiose. Devi impostarli su un valore sicuro o disattivarli completamente, come ti consiglio. Dovresti comunque usare percorsi assoluti. Se il controllo della contaminazione si lamenta di qualcos'altro, assicurati di aver contaminato i dati. Vedere la pagina man di perlsec per i dettagli.

Cosa succede quando lo esegui dalla riga di comando?

Lo script restituisce ciò che ti aspetti quando viene eseguito dalla riga di comando? L'output dell'intestazione è prima, seguito da una riga vuota? Ricorda che STDERRpotrebbe essere unito a STDOUT se sei su un terminale (ad esempio una sessione interattiva) e, a causa del buffering, potrebbe apparire in un ordine confuso. Attiva la funzione autoflush di Perl impostando $|un valore vero. Tipicamente potresti vedere $|++;nei programmi CGI. Una volta impostata, ogni stampa e scrittura andrà immediatamente all'output anziché essere memorizzata nel buffer. Devi impostarlo per ogni filehandle. Utilizzare selectper modificare il filehandle predefinito, in questo modo:

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

In ogni caso, la prima cosa in uscita dovrebbe essere l'intestazione CGI seguita da una riga vuota.

Cosa succede quando lo esegui dalla riga di comando con un ambiente simile a CGI?

L'ambiente del server Web è generalmente molto più limitato rispetto all'ambiente della riga di comando e contiene informazioni aggiuntive sulla richiesta. Se lo script viene eseguito correttamente dalla riga di comando, potresti provare a simulare un ambiente di server web. Se il problema appare, hai un problema ambientale.

Annulla o rimuovi queste variabili

  • PATH
  • LD_LIBRARY_PATH
  • tutte le ORACLE_*variabili

Imposta queste variabili

  • REQUEST_METHOD(impostato GET, HEADo POSTse del caso)
  • SERVER_PORT (impostato su 80, di solito)
  • REMOTE_USER (se stai facendo cose di accesso protetto)

Le versioni recenti di CGI.pm(> 2.75) richiedono il -debugflag per ottenere il vecchio (utile) comportamento, quindi potrebbe essere necessario aggiungerlo alle CGI.pmimportazioni.

use CGI qw(-debug)

Stai usando die()o warn?

Queste funzioni vengono stampate a STDERRmeno che non siano state ridefinite. Non producono neanche un'intestazione CGI. Puoi ottenere la stessa funzionalità con pacchetti come CGI :: Carp

Cosa succede dopo aver svuotato la cache del browser?

Se pensi che il tuo script stia facendo la cosa giusta e quando esegui la richiesta manualmente ottieni l'output giusto, il browser potrebbe essere il colpevole. Svuota la cache e imposta la dimensione della cache su zero durante il test. Ricorda che alcuni browser sono davvero stupidi e non ricaricano effettivamente nuovi contenuti anche se gli dici di farlo. Ciò è particolarmente diffuso nei casi in cui il percorso dell'URL è lo stesso, ma il contenuto cambia (ad es. Immagini dinamiche).

La sceneggiatura è dove pensi che sia?

Il percorso del file system di uno script non è necessariamente correlato direttamente al percorso dell'URL dello script. Assicurati di avere la directory giusta, anche se devi scrivere un breve script di test per verificarlo. Inoltre, sei sicuro di voler modificare il file corretto? Se non vedi alcun effetto con le tue modifiche, potresti modificare un file diverso o caricare un file nel posto sbagliato. (Questa è, tra l'altro, la mia causa più frequente di tali problemi;)

Stai usando CGI.pmo un suo derivato?

Se il problema è legato al parsing all'ingresso CGI e non si utilizza un modulo ampiamente testato come CGI.pm, CGI::Request, CGI::Simpleo CGI::Lite, utilizzare il modulo e andare avanti con la vita. CGI.pmha una cgi-lib.plmodalità di compatibilità che può aiutarti a risolvere i problemi di input dovuti alle vecchie implementazioni del parser CGI.

Hai usato percorsi assoluti?

Se stai eseguendo comandi esterni con system, segni di spunta o altri servizi IPC, dovresti usare un percorso assoluto al programma esterno. Non solo sai esattamente cosa stai eseguendo, ma eviti anche alcuni problemi di sicurezza. Se stai aprendo file per la lettura o la scrittura, usa un percorso assoluto. Lo script CGI potrebbe avere un'idea diversa di te sulla directory corrente. In alternativa, puoi fare un esplicito chdir()per metterti nel posto giusto.

Hai controllato i valori di ritorno?

La maggior parte delle funzioni Perl ti dirà se hanno funzionato o meno e si imposteranno $!in caso di fallimento. Hai controllato il valore restituito ed esaminato $!i messaggi di errore? Hai controllato $@se stavi usando eval?

Quale versione di Perl stai usando?

L'ultima versione stabile di Perl è la 5.28 (o meno, a seconda di quando è stata modificata l'ultima volta). Stai usando una versione precedente? Versioni differenti di Perl possono avere idee differenti sugli avvertimenti.

Quale server web stai utilizzando?

Server diversi possono agire in modo diverso nella stessa situazione. Lo stesso prodotto server può agire in modo diverso con configurazioni diverse. Includere quante più informazioni possibile in qualsiasi richiesta di aiuto.

Hai controllato la documentazione del server?

I programmatori CGI seri dovrebbero conoscere il più possibile il server, comprese non solo le caratteristiche e il comportamento del server, ma anche la configurazione locale. La documentazione per il server potrebbe non essere disponibile se si utilizza un prodotto commerciale. Altrimenti, la documentazione dovrebbe essere sul tuo server. In caso contrario, cercalo sul web.

Hai cercato negli archivi di comp.infosystems.www.authoring.cgi?

Questo era utile, ma tutti i buoni poster sono morti o si sono allontanati.

È probabile che qualcuno abbia già avuto il tuo problema e che qualcuno (forse io) abbia risposto in questo newsgroup. Sebbene questo newsgroup abbia superato il suo periodo di massimo splendore, la saggezza raccolta dal passato a volte può essere utile.

Puoi riprodurre il problema con un breve script di prova?

In sistemi di grandi dimensioni, potrebbe essere difficile rintracciare un bug poiché stanno accadendo così tante cose. Prova a riprodurre il comportamento del problema con lo script più breve possibile. Conoscere il problema è la maggior parte della soluzione. Questo può certamente richiedere molto tempo, ma non hai ancora trovato il problema e stai esaurendo le opzioni. :)

Hai deciso di andare a vedere un film?

Sul serio. A volte possiamo essere così presi dal problema che sviluppiamo un "restringimento percettivo" (visione a tunnel). Fare una pausa, prendere una tazza di caffè o far esplodere alcuni cattivi in ​​[Duke Nukem, Quake, Doom, Halo, COD] potrebbe darti la nuova prospettiva di cui hai bisogno per affrontare nuovamente il problema.

Hai vocalizzato il problema?

Di nuovo sul serio. A volte spiegare il problema ad alta voce ci porta alle nostre risposte. Parla con il pinguino (peluche) perché i tuoi colleghi non stanno ascoltando. Se sei interessato a questo come uno strumento di debug serio (e lo consiglio se non hai ancora trovato il problema), potresti anche leggere The Psychology of Computer Programming .


4
Non essere timido nel modificare la mia risposta se hai qualcosa da aggiungere.
brian d foy

Sembra che tu voglia rimuovere il link alla meta FAQ CGI. La 5.12.1 è considerata "stabile"?
Snake Plissken

1
Perché no $|=1invece di $|++?
reinierpost

Perché $|=1invece di $|++? Non fa davvero la differenza, e anche allora $|è magico.
brian d foy

2
Bella risposta, penso che potrebbe valere la pena ricordare che alcune di queste soluzioni dovrebbero essere esclusivamente per la risoluzione dei problemi e non essere inserite nel codice di produzione. use strictè generalmente buono da usare in ogni momento, mentre l'uso fatalsToBrowserpotrebbe non essere consigliato in produzione, specialmente se lo stai usando die.
vol7ron


7

Stai utilizzando un gestore degli errori durante il debug?

dievengono stampate istruzioni e altri errori irreversibili in fase di esecuzione e di compilazione STDERR, che possono essere difficili da trovare e possono essere confusi con messaggi provenienti da altre pagine Web del sito. Durante il debug dello script, è una buona idea visualizzare in qualche modo i messaggi di errore irreversibile nel browser.

Un modo per farlo è chiamare

   use CGI::Carp qw(fatalsToBrowser);

all'inizio del copione. Quella chiamata installerà un $SIG{__DIE__}gestore (vedi perlvar ) che mostrerà errori fatali nel tuo browser, anteponendogli un'intestazione valida se necessario. Un altro trucco di debug CGI che ho usato prima di aver mai sentito parlare è CGI::Carpstato quello di utilizzare evalcon le funzionalità DATAe __END__sullo script per rilevare gli errori in fase di compilazione:

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

Questa tecnica più dettagliata ha un leggero vantaggio CGI::Carpin quanto catturerà più errori in fase di compilazione.

Aggiornamento: non l'ho mai usato, ma sembra che CGI::Debug, come suggerito da Mikael S, sia anche uno strumento molto utile e configurabile per questo scopo.


3
@Ether: <DATA>è un filehandle magico che legge lo script corrente a partire da __END__. Join fornisce il contesto dell'elenco, quindi <fh> restituisce un array, una riga per elemento. Quindi unisci lo rimette insieme (unendolo con ""). Infine, eval.
derobert

@Ether: Un modo più leggibile per scrivere la riga 2 sarebbe:eval join(q{}, <DATA>);
derobert

@derobert: in realtà, __DATA__ è il token utilizzato per avviare la sezione dati, non __END__ (penso che fosse la mia confusione).
Ether

1
@Ether: Beh, in realtà, funzionano entrambi nello script di primo livello (secondo la manpage di perldata). Ma poiché si preferisce DATA , ho cambiato la risposta.
derobert

@derobert: grazie per il collegamento alla documentazione; Non sapevo del comportamento di retrocompatibilità di __END__!
Ether

7

Mi chiedo come mai nessuno abbia menzionato l' PERLDB_OPTSopzione chiamata RemotePort; sebbene, ammettiamolo, non ci siano molti esempi funzionanti sul web ( RemotePortnon è nemmeno menzionato in perldebug ) - ed è stato un po 'problematico per me trovare questo, ma eccolo qui (essendo un esempio Linux).

Per fare un esempio corretto, prima avevo bisogno di qualcosa che potesse fare una simulazione molto semplice di un server web CGI, preferibilmente attraverso una singola riga di comando. Dopo aver trovato un semplice server web a riga di comando per eseguire cgis. (perlmonks.org) , ho trovato IO :: All - A Tiny Web Server applicabile per questo test.

Qui, lavorerò nella /tmpdirectory; lo script CGI sarà /tmp/test.pl(incluso di seguito). Nota che il IO::Allserver servirà solo file eseguibili nella stessa directory di CGI, quindi chmod +x test.plè richiesto qui. Quindi, per eseguire la solita esecuzione di test CGI, cambio directory in /tmpnel terminale ed eseguo il server web one-liner lì:

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

Il comando webserver si bloccherà nel terminale e altrimenti avvierà il server web localmente (su 127.0.0.1 o localhost) - successivamente, posso andare su un browser web e richiedere questo indirizzo:

http://127.0.0.1:8080/test.pl

... e dovrei osservare gli prints creati test.plcaricandoli - e mostrandoli - nel browser web.


Ora, per eseguire il debug di questo script RemotePort, prima abbiamo bisogno di un listener sulla rete, attraverso il quale interagiremo con il debugger Perl; possiamo usare lo strumento della riga di comando netcat( nc, visto che qui: Perl 如何 debug remoto? ). Quindi, prima esegui il netcatlistener in un terminale, dove si bloccherà e attenderà le connessioni sulla porta 7234 (che sarà la nostra porta di debug):

$ nc -l 7234

Quindi, vorremmo perliniziare in modalità debug con RemotePort, quando test.plè stato chiamato (anche in modalità CGI, tramite il server). Questo, in Linux, può essere fatto usando il seguente script "shebang wrapper" - che anche qui deve essere dentro /tmpe deve essere reso eseguibile:

cd /tmp

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

chmod +x perldbgcall.sh

Questa è una cosa complicata - vedi script di shell - Come posso usare le variabili d'ambiente nel mio shebang? - Scambio di stack Unix e Linux . Ma il trucco qui sembra non essere quello di fare il fork perldell'interprete che gestisce test.pl - quindi una volta che lo abbiamo premuto, non lo facciamo exec, ma invece chiamiamo perl"chiaramente" e fondamentalmente "sorgente" il nostro test.plscript usando do(vedi Come faccio a eseguire un Script Perl dall'interno di uno script Perl? ).

Ora che abbiamo perldbgcall.shin /tmp- siamo in grado di cambiare il test.plfile, in modo che faccia riferimento a questo file eseguibile sulla sua linea di shebang (invece del solito interprete Perl) - qui viene /tmp/test.plmodificato in tal modo:

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

Ora, entrambi test.ple il suo nuovo gestore di shebang,, perldbgcall.shsono dentro /tmp; e dobbiamo ncascoltare le connessioni di debug sulla porta 7234, così possiamo finalmente aprire un'altra finestra di terminale, cambiare directory in /tmped eseguire il server web one-liner (che ascolterà le connessioni web sulla porta 8080) lì:

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

Dopo aver fatto questo, siamo in grado di andare al nostro browser web, e richiedere lo stesso indirizzo, http://127.0.0.1:8080/test.pl. Tuttavia, ora quando il server web tenta di eseguire lo script, lo farà tramite perldbgcall.shshebang, che verrà avviato perlin modalità debugger remoto. Pertanto, l'esecuzione dello script verrà sospesa e quindi il browser Web si bloccherà, in attesa dei dati. Ora possiamo passare al netcatterminale e dovremmo vedere il familiare testo del debugger Perl, tuttavia, l'output tramite 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>

Come mostra lo snippet, ora lo usiamo fondamentalmente nccome un "terminale" - quindi possiamo digitare r(e Invio) per "esegui" - e lo script eseguirà l'istruzione breakpoint (vedi anche In perl, qual è la differenza tra $ DB :: single = 1 e 2? ), Prima di fermarsi nuovamente (nota che a quel punto il browser si bloccherà comunque).

Quindi, ora possiamo, diciamo, passare attraverso il resto di test.pl, attraverso il ncterminale:

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

... comunque, anche a questo punto, il browser si blocca e attende i dati. Solo dopo essere usciti dal debugger con q:

  DB<1> q
$

... il browser smette di bloccarsi e infine visualizza l'output (completo) di test.pl:

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

Ovviamente, questo tipo di debug può essere eseguito anche senza eseguire il server web - tuttavia, la cosa bella qui, è che non tocchiamo affatto il server web; attiviamo l'esecuzione "nativamente" (per CGI) da un browser web - e l'unica modifica necessaria nello stesso script CGI, è la modifica di shebang (e, ovviamente, la presenza dello script wrapper shebang, come file eseguibile nello stesso directory).

Bene, spero che questo aiuti qualcuno - di sicuro mi sarebbe piaciuto essere incappato in questo, invece di scriverlo da solo. :)
Ciao!


5

Per me, utilizzo log4perl . È abbastanza utile e facile.

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

Onestamente puoi fare tutte le cose divertenti sopra questo post. Tuttavia, la soluzione più semplice e proattiva che ho trovato è stata semplicemente "stamparlo".

In esempio: (codice normale)

`$somecommand`;

Per vedere se sta facendo quello che voglio davvero che faccia: (Risoluzione dei problemi)

print "$somecommand";

1

Probabilmente vale anche la pena ricordare che Perl ti dirà sempre su quale riga si verifica l'errore quando esegui lo script Perl dalla riga di comando. (Una sessione SSH per esempio)

Di solito lo farò se tutto il resto fallisce. Inserirò SSH nel server ed eseguirò manualmente lo script Perl. Per esempio:

% perl myscript.cgi 

Se c'è un problema, Perl te lo dirà. Questo metodo di debug elimina qualsiasi problema relativo alle autorizzazioni dei file o problemi del browser Web o del server Web.


Perl non ti dice sempre il numero di riga in cui si verifica un errore. Ti dice il numero di riga in cui si rende conto che c'è qualcosa che non va. Probabilmente l'errore si è già verificato.
brian d foy

0

È possibile eseguire lo script cgi perl nel terminale utilizzando il comando seguente

 $ perl filename.cgi

Interpreta il codice e fornisce il risultato con il codice HTML. Riporterà l'eventuale errore.


1
Spiacenti, il comando $ perl -c filename.cgi convalida la sintassi del codice e segnala l'eventuale errore. Non fornirà il codice html del cgi.
D.Karthikeyan

L'invocazione perl -c filenameverificherà solo la sintassi. Ma perl filenamestampa l'output HTML. Non è garantito che non ci sarà un errore 500 CGI, ma è un buon primo test.
Nagev
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.