web-dev-qa-db-fra.com

Comment puis-je résoudre mon script Perl CGI?

J'ai un script Perl qui ne fonctionne pas et je ne sais pas comment commencer à cerner le problème. Que puis-je faire?


Note: J'ajoute la question parce que je veux vraiment ajouter ma très longue réponse à Stackoverflow. Je continue à y renvoyer des liens externes dans d'autres réponses et il mérite d'être ici. Ne soyez pas timide pour éditer ma réponse si vous avez quelque chose à ajouter.

97
brian d foy

Cette réponse est conçue comme un cadre général permettant de résoudre les problèmes liés aux scripts Perl CGI. Elle figurait à l'origine sur Perlmonks sous la forme Résolution des problèmes liés aux scripts Perl CGI . Ce n'est pas un guide complet de tous les problèmes que vous pouvez rencontrer, ni un tutoriel sur la suppression de bogues. C'est l'aboutissement de mon expérience de débogage de scripts CGI depuis plus de vingt ans. Cette page semble avoir eu beaucoup de maisons différentes, et je semble oublier qu'elle existe, alors je l'ajoute à StackOverflow. Vous pouvez envoyer vos commentaires ou suggestions à l'adresse [email protected]. C'est aussi un wiki de communauté, mais n'allez pas trop cinglé. :)


Utilisez-vous les fonctionnalités intégrées de Perl pour vous aider à trouver des problèmes?

Activez les avertissements pour permettre à Perl de vous avertir des parties discutables de votre code. Vous pouvez le faire depuis la ligne de commande avec le commutateur _-w_ afin de ne pas avoir à changer de code ou à ajouter un pragma à chaque fichier:

_ % Perl -w program.pl
_

Cependant, vous devez vous efforcer de toujours supprimer le code douteux en ajoutant le pragma warnings à tous vos fichiers:

_ use warnings;
_

Si vous avez besoin de plus d'informations que le bref message d'avertissement, utilisez le pragma diagnostics pour obtenir plus d'informations ou consultez la documentation perldiag :

_ use diagnostics;
_

Avez-vous d'abord généré un en-tête CGI valide?

Le serveur s'attend à ce que la première sortie d'un script CGI soit l'en-tête CGI. Cela peut généralement être aussi simple que _print "Content-type: text/plain\n\n";_ ou avec CGI.pm et ses dérivés, print header(). Certains serveurs sont sensibles aux erreurs générées (sur STDERR) avant la sortie standard (sur STDOUT).

Essayez d'envoyer des erreurs au navigateur

Ajouter cette ligne

_ use CGI::Carp 'fatalsToBrowser';
_

à votre script. Cela envoie également des erreurs de compilation à la fenêtre du navigateur. Assurez-vous de le supprimer avant de passer à un environnement de production, car les informations supplémentaires peuvent constituer un risque pour la sécurité.

Que dit le journal des erreurs?

Les serveurs conservent les journaux d'erreurs (ou devraient le faire au moins). Les erreurs de sortie du serveur et de votre script doivent apparaître ici. Trouvez le journal des erreurs et voyez ce qu'il dit. Il n'y a pas d'emplacement standard pour les fichiers journaux. Recherchez l'emplacement dans la configuration du serveur ou demandez à l'administrateur du serveur. Vous pouvez également utiliser des outils tels que CGI :: Carp pour conserver vos propres fichiers journaux.

Quelles sont les autorisations du script?

Si vous voyez des erreurs telles que "Autorisation refusée" ou "Méthode non implémentée", cela signifie probablement que votre script n'est pas lisible et exécutable par l'utilisateur du serveur Web. Sur les différentes versions d’Unix, il est recommandé de passer en mode 755: _chmod 755 filename_. Ne définissez jamais un mode sur 777!

Utilisez-vous _use strict_?

Rappelez-vous que Perl crée automatiquement des variables lorsque vous les utilisez pour la première fois. Ceci est une fonctionnalité, mais peut parfois causer des bugs si vous tapez un nom de variable. Le pragma use strict vous aidera à trouver ce genre d’erreurs. C'est agaçant jusqu'à ce que vous vous y habituiez, mais votre programmation s'améliorera considérablement après un certain temps et vous serez libre de commettre des erreurs différentes.

Le script est-il compilé?

Vous pouvez vérifier les erreurs de compilation à l'aide du commutateur _-c_. Concentrez-vous sur les premières erreurs signalées. Rincez, répétez. Si vous obtenez des erreurs vraiment étranges, vérifiez que votre script se termine bien. Si vous utilisez FTP en mode binaire, extrayez-vous à partir de CVS, ou quelque chose d'autre qui ne gère pas la traduction de fin de ligne, le serveur Web peut voir votre script sous la forme d'une grosse ligne. Transférez les scripts Perl en mode ASCII.

Le script se plaint-il de dépendances non sécurisées?

Si votre script se plaint de dépendances non sécurisées, vous utilisez probablement le commutateur _-T_ pour activer le mode altération, ce qui est une bonne chose, car il vous permet de transmettre des données non contrôlées au shell. S'il se plaint, il fait son travail pour nous aider à écrire des scripts plus sécurisés. Toutes les données provenant de l'extérieur du programme (c'est-à-dire l'environnement) sont considérées comme contaminées. Les variables d'environnement telles que PATH et _LD_LIBRARY_PATH_ sont particulièrement gênantes. Vous devez les définir sur une valeur sûre ou les supprimer complètement, comme je le recommande. Vous devriez quand même utiliser des chemins absolus. Si la vérification de la souillure se plaint de quelque chose d'autre, assurez-vous que les données ont été intactes. Voir la page de manuel perlsec pour plus de détails.

Que se passe-t-il lorsque vous l'exécutez à partir de la ligne de commande?

Le script affiche-t-il ce que vous attendez lorsqu'il est exécuté à partir de la ligne de commande? L'en-tête est-il affiché en premier, suivi d'une ligne vide? Rappelez-vous que STDERR peut être fusionné avec STDOUT si vous êtes sur un terminal (par exemple, une session interactive) et que, du fait de la mise en mémoire tampon, il peut apparaître dans un ordre mélangé. Activez la fonction de balayage automatique de Perl en définissant _$|_ sur une valeur vraie. En règle générale, vous pouvez voir _$|++;_ dans les programmes CGI. Une fois défini, chaque impression et écriture ira immédiatement à la sortie plutôt que d'être mise en mémoire tampon. Vous devez définir ceci pour chaque descripteur de fichier. Utilisez select pour modifier le descripteur de fichier par défaut, comme suit:

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

Quoi qu'il en soit, la première chose à produire devrait être l'en-tête CGI suivi d'une ligne vide.

Que se passe-t-il lorsque vous l'exécutez à partir de la ligne de commande avec un environnement de type CGI?

L'environnement du serveur Web est généralement beaucoup plus limité que votre environnement de ligne de commande et contient des informations supplémentaires sur la requête. Si votre script fonctionne correctement à partir de la ligne de commande, vous pouvez essayer de simuler un environnement de serveur Web. Si le problème apparaît, vous avez un problème d'environnement.

Désactiver ou supprimer ces variables

  • PATH
  • _LD_LIBRARY_PATH_
  • toutes les variables _Oracle_*_

Définir ces variables

  • _REQUEST_METHOD_ (défini sur GET, HEAD ou POST selon le cas)
  • _SERVER_PORT_ (généralement 80)
  • _REMOTE_USER_ (si vous effectuez des tâches d'accès protégé)

Les versions récentes de _CGI.pm_ (> 2.75) requièrent le drapeau _-debug_ pour obtenir l'ancien comportement (utile). Vous devrez donc peut-être l'ajouter à vos importations _CGI.pm_.

_use CGI qw(-debug)
_

Utilisez-vous die() ou warn?

Ces fonctions sont imprimées sur STDERR sauf si vous les avez redéfinies. Ils ne produisent pas non plus d'en-tête CGI. Vous pouvez obtenir la même fonctionnalité avec des packages tels que CGI :: Carp

Que se passe-t-il une fois le cache du navigateur effacé?

Si vous pensez que votre script fait le bon choix et que lorsque vous exécutez la demande manuellement, vous obtenez le bon résultat, le navigateur peut en être la cause. Effacez le cache et définissez la taille du cache sur zéro lors des tests. N'oubliez pas que certains navigateurs sont vraiment stupides et ne rechargeront pas de nouveau contenu même si vous leur dites de le faire. Cela est particulièrement fréquent dans les cas où le chemin d’URL est le même, mais que le contenu change (par exemple, des images dynamiques).

Le script est-il tel que vous le pensez?

Le chemin du système de fichiers d’un script n’est pas nécessairement directement lié au chemin d’URL du script. Assurez-vous que vous avez le bon répertoire, même si vous devez écrire un court script de test pour le vérifier. De plus, êtes-vous sûr de modifier le bon fichier? Si vous ne voyez aucun effet avec vos modifications, vous pouvez modifier un fichier différent ou télécharger un fichier au mauvais endroit. (C’est d’ailleurs la cause la plus fréquente de ces problèmes;)

Utilisez-vous CGI.pm , ou un dérivé de celui-ci?

Si votre problème est lié à l'analyse de l'entrée CGI et que vous n'utilisez pas de module largement testé comme _CGI.pm_, CGI::Request , CGI::Simple ou CGI::Lite , utilisez le module et poursuivez votre vie. _CGI.pm_ possède un mode de compatibilité _cgi-lib.pl_ qui peut vous aider à résoudre les problèmes de saisie dus à des implémentations plus anciennes de l'analyseur CGI.

Avez-vous utilisé des chemins absolus?

Si vous exécutez des commandes externes avec system, des ticks arrière ou d'autres fonctionnalités IPC, vous devez utiliser un chemin absolu vers le programme externe. Non seulement vous savez exactement ce que vous utilisez, mais vous évitez également certains problèmes de sécurité. Si vous ouvrez des fichiers en lecture ou en écriture, utilisez un chemin absolu. Le script CGI peut avoir une idée différente du répertoire actuel sur vous. Alternativement, vous pouvez faire un chdir() explicite pour vous mettre au bon endroit.

Avez-vous vérifié vos valeurs de retour?

La plupart des fonctions Perl vous diront si elles ont fonctionné ou non et définiront _$!_ en cas d'échec. Avez-vous vérifié la valeur de retour et examiné _$!_ pour les messages d'erreur? Avez-vous vérifié _$@_ si vous utilisiez eval?

Quelle version de Perl utilisez-vous?

La dernière version stable de Perl est la 5.28 (ou pas, selon la dernière édition). Utilisez-vous une version plus ancienne? Différentes versions de Perl peuvent avoir différentes idées d'avertissements.

Quel serveur web utilisez-vous?

Des serveurs différents peuvent agir différemment dans la même situation. Le même produit serveur peut agir différemment avec des configurations différentes. Incluez autant d'informations que possible dans toute demande d'aide.

Avez-vous vérifié la documentation du serveur?

Les programmeurs CGI sérieux doivent en savoir le plus possible sur le serveur - y compris non seulement les caractéristiques et le comportement du serveur, mais également la configuration locale. La documentation de votre serveur peut ne pas être disponible si vous utilisez un produit commercial. Sinon, la documentation devrait être sur votre serveur. Si ce n'est pas le cas, cherchez-le sur le Web.

Avez-vous effectué une recherche dans les archives de comp.infosystems.www.authoring.cgi ?

Cette utilisation est utile, mais toutes les bonnes affiches sont mortes ou ont disparu.

Il est probable que quelqu'un ait déjà eu votre problème et que quelqu'un (éventuellement moi) y ait répondu dans ce groupe de discussion. Bien que ce groupe de discussion ait passé son heure de gloire, la sagesse recueillie du passé peut parfois être utile.

Pouvez-vous reproduire le problème avec un script de test court?

Dans les grands systèmes, il peut être difficile de localiser un bogue car il se passe tellement de choses. Essayez de reproduire le problème avec le script le plus court possible. Connaître le problème est la majeure partie de la solution. Cela peut prendre beaucoup de temps, mais vous n'avez pas encore trouvé le problème et vous manquez d'options. :)

Avez-vous décidé d'aller voir un film?

Sérieusement. Parfois, nous pouvons être tellement absorbés par le problème que nous développons un "rétrécissement perceptuel" (vision tunnel). Prendre une pause, prendre une tasse de café ou lancer un flingue dans [Duke Nukem, Quake, Doom, Halo, COD] pourrait vous donner une nouvelle perspective de la nécessité de ré-aborder le problème.

Avez-vous exprimé le problème?

Sérieusement encore. Parfois, expliquer le problème à haute voix nous amène à nos propres réponses. Parlez au pingouin (peluche) car vos collègues ne vous écoutent pas. Si cela vous intéresse comme outil de débogage sérieux (et que je le recommande si vous n'avez pas encore trouvé le problème), vous pouvez aussi lire La psychologie de la programmation informatique .

125
brian d foy

Je pense que CGI :: Debug mérite également d’être mentionné.

10
Mikael S

Je me demande comment se fait-il que personne n'ait mentionné l'option PERLDB_OPTS appelée RemotePort; Certes, il n’ya pas beaucoup d’exemples de travail sur le Web (RemotePort n’est même pas mentionné dans perldebug ) - et c’était un peu problématique pour moi de proposer celui-ci, mais le reste (il être un exemple de Linux).

Pour faire un bon exemple, il me fallait d’abord quelque chose qui puisse simuler très simplement un serveur Web CGI, de préférence via une seule ligne de commande. Après avoir trouvé serveur Web en ligne de commande simple pour l'exécution de cgis. (Perlmonks.org) }, j'ai trouvé que IO :: All - Un serveur Web minuscule était applicable pour ce test.

Ici, je vais travailler dans le répertoire /tmp; le script CGI sera /tmp/test.pl (inclus ci-dessous). Notez que le serveur IO::All servira uniquement les fichiers exécutables dans le même répertoire que CGI, donc chmod +x test.pl est requis ici. Donc, pour effectuer le test de test CGI habituel, je change de répertoire en /tmp dans le terminal et lance le serveur Web one-liner à cet emplacement:

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

La commande webserver va bloquer dans le terminal et sinon, démarrer le serveur Web localement (sur 127.0.0.1 ou localhost). Ensuite, je peux accéder à un navigateur Web et demander cette adresse:

http://127.0.0.1:8080/test.pl

... et je devrais observer la prints faite par test.pl en cours de chargement - et affichée - dans le navigateur Web.


Maintenant, pour déboguer ce script avec RemotePort, nous avons d’abord besoin d’un auditeur sur le réseau, à travers lequel nous allons interagir avec le débogueur Perl; nous pouvons utiliser l'outil de ligne de commande netcat (nc, vu qu'ici: Perl 如何 debug distant? ). Donc, lancez d’abord l’auditeur netcat dans un terminal - où il bloquera et attendra les connexions sur le port 7234 (qui sera notre port de débogage):

$ nc -l 7234

Ensuite, nous voudrions que Perl démarre en mode débogage avec RemotePort, lorsque le test.pl a été appelé (même en mode CGI, via le serveur). Ceci, sous Linux, peut être fait en utilisant le script "Shebang wrapper" suivant - qui doit ici aussi être en /tmp, et doit être rendu exécutable:

cd /tmp

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

chmod +x perldbgcall.sh

C’est un peu délicat - voir Script shell - Comment utiliser des variables d’environnement dans un shebang? - Unix et Linux Stack Exchange . Mais l’astuce ici semble être pas de bifurquer l’interprète Perl qui gère test.pl - donc une fois que nous l’avons trouvé, nous ne sommes pas exec, mais au lieu de cela nous appelons Perl "clairement", et fondamentalement "source" notre test.pl script utilisant do (voir Comment exécuter un script Perl à partir d'un script Perl? ).

Maintenant que nous avons perldbgcall.sh dans /tmp - nous pouvons modifier le fichier test.pl, de sorte qu'il se réfère à ce fichier exécutable sur sa ligne Shebang (au lieu de l'interpréteur Perl habituel) - voici /tmp/test.pl modifié de la manière suivante:

#!./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 et son nouveau gestionnaire Shebang, perldbgcall.sh, sont maintenant dans /tmp; et nous avons nc à l'écoute des connexions de débogage sur le port 7234 - nous pouvons donc enfin ouvrir une autre fenêtre de terminal, changer le répertoire en /tmp et exécuter le serveur Web one-liner (qui écoutera les connexions Web sur le port 8080):

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

Ceci fait, nous pouvons accéder à notre navigateur Web et demander la même adresse, http://127.0.0.1:8080/test.pl. Cependant, lorsque le serveur Web essaie d'exécuter le script, il le fera par le biais de perldbgcall.sh Shebang - qui démarrera Perl en mode débogueur distant. Ainsi, l’exécution du script sera suspendue et le navigateur Web se verrouillera en attendant les données. Nous pouvons maintenant basculer vers le terminal netcat et nous devrions voir le texte familier du débogueur Perl - toutefois, le résultat est transmis via 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>

Comme le montre l'extrait de code, nous utilisons maintenant essentiellement nc comme "terminal" - de sorte que nous puissions taper r (et Entrée) pour "exécuter" - et le script s'exécutera avec l'instruction breakpoint (voir aussi en Perl, est la différence entre $ DB :: single = 1 et 2? ), avant de vous arrêter à nouveau (remarque: le navigateur sera toujours verrouillé).

Ainsi, nous pouvons maintenant, disons, parcourir le reste de test.pl, via le terminal 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>

... cependant, à ce stade également, le navigateur se verrouille et attend des données. Seulement après avoir quitté le débogueur avec q:

  DB<1> q
$

... le navigateur arrête-t-il le verrouillage - et affiche finalement la sortie (complète) de test.pl:

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

Bien sûr, ce type de débogage peut être effectué même sans exécuter le serveur Web - toutefois, la chose intéressante ici est que nous ne touchons pas du tout le serveur Web; nous déclenchons l'exécution "de manière native" (pour CGI) depuis un navigateur Web - et le seul changement nécessaire dans le script CGI lui-même est le changement de Shebang (et bien sûr, la présence du script d'encapsulation Shebang, en tant que fichier exécutable dans le même fichier annuaire).

Eh bien, espérons que cela aide quelqu'un - j'aurais vraiment aimé tomber par hasard là-dessus, au lieu de l'écrire moi-même :)
À votre santé!

8
sdaau

Utilisez-vous un gestionnaire d’erreurs pendant le débogage?

Les instructions die et autres erreurs fatales au moment de l'exécution et de la compilation génèrent imprimé en STDERR, ce qui peut être difficile à trouver et peut être confondu avec les messages d'autres pages Web de votre site. Pendant le débogage de votre script , Il est judicieux d'afficher les messages d'erreur fatale dans votre navigateur D'une manière ou d'une autre.

Une façon de faire est d'appeler

   use CGI::Carp qw(fatalsToBrowser);

en haut de votre script. Cet appel installera un gestionnaire $SIG{__DIE__} (voir perlvar ) et affichera les erreurs fatales dans votre navigateur, en le précédant d'un en-tête valide si nécessaire. Une autre astuce de débogage CGI que j'ai utilisée avant d'avoir entendu parler de CGI::Carp consistait à Utiliser eval avec les fonctionnalités DATA et __END__ du script pour intercepter les erreurs de compilation:

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

Cette technique plus détaillée présente un léger avantage par rapport à CGI::Carp, car elle intercepte davantage d’erreurs de compilation.

Update: Je ne l'ai jamais utilisé, mais il semble que CGI::Debug , comme le suggère Mikael S , Est également un outil très utile et configurable à cet effet.

7
mob

Pour moi, j'utilise log4Perl . C'est très utile et 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");
5
zawhtut

Honnêtement, vous pouvez faire toutes les choses amusantes ci-dessus. Bien que la solution la plus simple et la plus proactive que j’ai trouvée était simplement de "l’imprimer".

Dans l'exemple: (Code normal)

`$somecommand`;

Pour voir si je fais ce que je veux vraiment: (Dépannage)

print "$somecommand";
1
Ilan Kleiman

Il convient également de mentionner que Perl vous indiquera toujours sur quelle ligne l’erreur se produit lorsque vous exécutez le script Perl à partir de la ligne de commande. (Une session SSH par exemple)

Je le ferai généralement si tout le reste échoue. Je vais SSH sur le serveur et exécuter manuellement le script Perl. Par exemple:

% Perl myscript.cgi 

S'il y a un problème, Perl vous en informera. Cette méthode de débogage supprime les problèmes liés aux autorisations de fichiers, aux problèmes de navigateur Web ou de serveur Web.

1
gpwr

Vous pouvez exécuter le script cgi Perl dans terminal en utilisant la commande ci-dessous

 $ Perl filename.cgi

Il interprète le code et fournit le résultat avec le code HTML. Il signalera l'erreur, le cas échéant. 

0
D.Karthikeyan