¿Cómo puedo solucionar problemas de mi script CGI de Perl?

100

Tengo un script en Perl que no funciona y no sé cómo empezar a reducir el problema. ¿Que puedo hacer?


Nota: Estoy agregando la pregunta porque realmente quiero agregar mi respuesta muy larga a Stackoverflow. Sigo vinculándome externamente en otras respuestas y merece estar aquí. No dude en editar mi respuesta si tiene algo que agregar.

brian d foy
fuente
5
@Evan - mi punto es simplemente que incluso como no-CW todavía es editable - si tienes suficiente karma; 100 para CW, 2k en caso contrario. Entonces, ahora que tiene 2060, debería poder editar publicaciones que no sean de CW.
Marc Gravell
1
@Evan, los puntos mágicos se enumeran en la información sobre herramientas en la columna de la derecha aquí: stackoverflow.com/privileges
cjm
Si su navegador web muestra ruido de línea, es posible que en su lugar esté imprimiendo el script perl. En ese caso, consulte stackoverflow.com/questions/2621161/…
Andrew Grimm

Respuestas:

129

Esta respuesta está pensada como un marco general para resolver problemas con los scripts CGI de Perl y apareció originalmente en Perlmonks como Troubleshooting Perl CGI Scripts . No es una guía completa de todos los problemas que pueda encontrar, ni un tutorial sobre la eliminación de errores. Es solo la culminación de mi experiencia depurando scripts CGI durante veinte (¡más!) Años. Esta página parece haber tenido muchos hogares diferentes, y parece que me olvido de que existe, así que la estoy agregando a StackOverflow. Puede enviarme cualquier comentario o sugerencia a [email protected]. También es un wiki de la comunidad, pero no te vuelvas loco. :)


¿Está utilizando las funciones integradas de Perl para ayudarlo a encontrar problemas?

Active las advertencias para que Perl le advierta sobre partes cuestionables de su código. Puede hacer esto desde la línea de comando con el -winterruptor para que no tenga que cambiar ningún código o agregar un pragma a cada archivo:

 % perl -w program.pl

Sin embargo, debe obligarse a aclarar siempre el código cuestionable agregando el warningspragma a todos sus archivos:

 use warnings;

Si necesita más información que el breve mensaje de advertencia, use diagnosticspragma para obtener más información o busque en la documentación de perldiag :

 use diagnostics;

¿Produjo primero un encabezado CGI válido?

El servidor espera que la primera salida de un script CGI sea el encabezado CGI. Típicamente que podría ser tan simple como print "Content-type: text/plain\n\n";o con CGI.pm y sus derivados, print header(). Algunos servidores son sensibles a la salida de error (activada STDERR) que aparece antes que la salida estándar (activada STDOUT).

Intente enviar errores al navegador

Agregar esta línea

 use CGI::Carp 'fatalsToBrowser';

a tu guión. Esto también envía errores de compilación a la ventana del navegador. Asegúrese de eliminar esto antes de pasar a un entorno de producción, ya que la información adicional puede representar un riesgo para la seguridad.

¿Qué decía el registro de errores?

Los servidores mantienen registros de errores (o deberían, al menos). La salida de error del servidor y de su script debería aparecer allí. Busque el registro de errores y vea lo que dice. No existe un lugar estándar para los archivos de registro. Busque en la configuración del servidor su ubicación o pregunte al administrador del servidor. También puede utilizar herramientas como CGI :: Carp para mantener sus propios archivos de registro.

¿Cuáles son los permisos del script?

Si ve errores como "Permiso denegado" o "Método no implementado", probablemente significa que el usuario del servidor web no puede leer ni ejecutar su script. En las variantes de Unix, se recomienda cambiar el modo a 755: chmod 755 filename. ¡Nunca establezca un modo en 777!

¿Usted está utilizando use strict?

Recuerde que Perl crea automáticamente variables cuando las usa por primera vez. Esta es una característica, pero a veces puede causar errores si escribe mal el nombre de una variable. El pragma use strictle ayudará a encontrar ese tipo de errores. Es molesto hasta que te acostumbras, pero tu programación mejorará significativamente después de un tiempo y podrás cometer diferentes errores.

¿Se compila el script?

Puede comprobar si hay errores de compilación utilizando el -c conmutador. Concéntrese en los primeros errores notificados. Enjuague, repita. Si está recibiendo errores realmente extraños, verifique que su script tenga los finales de línea correctos. Si utiliza FTP en modo binario, realiza el pago desde CVS o cualquier otra cosa que no maneja la traducción del final de línea, el servidor web puede ver su script como una línea grande. Transfiera scripts de Perl en modo ASCII.

¿El script se queja de dependencias inseguras?

Si su secuencia de comandos se queja de dependencias inseguras, probablemente esté usando el -Tinterruptor para activar el modo de corrupción, lo cual es bueno ya que le mantiene pasando datos no verificados al shell. Si se queja, está haciendo su trabajo para ayudarnos a escribir scripts más seguros. Cualquier dato que se origine fuera del programa (es decir, el medio ambiente) se considera contaminado. Las variables de entorno como PATHy LD_LIBRARY_PATH son particularmente problemáticas. Debe configurarlos en un valor seguro o desarmarlos por completo, como recomiendo. De todos modos, deberías usar rutas absolutas. Si la comprobación de manchas se queja de otra cosa, asegúrese de haber limpiado los datos. Consulte la página del manual de perlsec para obtener más detalles.

¿Qué sucede cuando lo ejecuta desde la línea de comandos?

¿El script genera lo que espera cuando se ejecuta desde la línea de comando? ¿La salida del encabezado es primero, seguida de una línea en blanco? Recuerde que STDERRpuede fusionarse con STDOUT si está en una terminal (por ejemplo, una sesión interactiva) y, debido al almacenamiento en búfer, puede aparecer en un orden desordenado. Active la función de descarga automática de Perl estableciendo $|un valor real. Por lo general, es posible que vea $|++;en programas CGI. Una vez configuradas, cada impresión y escritura irá inmediatamente a la salida en lugar de almacenarse en búfer. Debe configurar esto para cada identificador de archivo. Úselo selectpara cambiar el identificador de archivo predeterminado, así:

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

De cualquier manera, la primera salida debería ser el encabezado CGI seguido de una línea en blanco.

¿Qué sucede cuando lo ejecuta desde la línea de comandos con un entorno similar a CGI?

El entorno del servidor web suele ser mucho más limitado que el entorno de la línea de comandos y tiene información adicional sobre la solicitud. Si su secuencia de comandos se ejecuta bien desde la línea de comandos, puede intentar simular un entorno de servidor web. Si aparece el problema, tiene un problema medioambiental.

Desarmar o eliminar estas variables

  • PATH
  • LD_LIBRARY_PATH
  • todas las ORACLE_*variables

Establecer estas variables

  • REQUEST_METHOD(establecido en GET, HEADo POSTsegún el caso)
  • SERVER_PORT (establecido en 80, generalmente)
  • REMOTE_USER (si está haciendo cosas de acceso protegido)

Las versiones recientes de CGI.pm(> 2.75) requieren que el -debugindicador obtenga el comportamiento anterior (útil), por lo que es posible que deba agregarlo a sus CGI.pmimportaciones.

use CGI qw(-debug)

¿Estás usando die()o warn?

Esas funciones se imprimen en a STDERRmenos que las haya redefinido. Tampoco generan un encabezado CGI. Puede obtener la misma funcionalidad con paquetes como CGI :: Carp

¿Qué sucede después de borrar la memoria caché del navegador?

Si cree que su script está haciendo lo correcto y cuando realiza la solicitud manualmente obtiene el resultado correcto, el navegador podría ser el culpable. Borre el caché y establezca el tamaño del caché en cero durante la prueba. Recuerde que algunos navegadores son realmente estúpidos y no volverán a cargar contenido nuevo aunque usted le diga que lo haga. Esto es especialmente frecuente en los casos en que la ruta URL es la misma, pero el contenido cambia (por ejemplo, imágenes dinámicas).

¿Está el guión donde crees que está?

La ruta del sistema de archivos a un script no está necesariamente relacionada directamente con la ruta URL del script. Asegúrese de tener el directorio correcto, incluso si tiene que escribir un breve script de prueba para probarlo. Además, ¿está seguro de que está modificando el archivo correcto? Si no ve ningún efecto con sus cambios, es posible que esté modificando un archivo diferente o cargando un archivo en el lugar equivocado. (Esta es, por cierto, mi causa más frecuente de tal problema;)

¿Está usando CGI.pm, o un derivado de él?

Si su problema está relacionado con el análisis de la entrada de CGI y no está usando un módulo ampliamente probado como CGI.pm, CGI::Request, CGI::Simpleo CGI::Lite, utilice el módulo y continuar con su vida. CGI.pmtiene un cgi-lib.plmodo de compatibilidad que puede ayudarlo a resolver problemas de entrada debido a implementaciones de analizador CGI más antiguas.

¿Usaste caminos absolutos?

Si está ejecutando comandos externos con systemretrocesos u otras funciones de IPC, debe usar una ruta absoluta al programa externo. No solo sabe exactamente lo que está ejecutando, sino que también evita algunos problemas de seguridad. Si abre archivos para leer o escribir, utilice una ruta absoluta. El script CGI puede tener una idea diferente a la tuya sobre el directorio actual. Alternativamente, puede hacer un explícito chdir()para ponerlo en el lugar correcto.

¿Verificó sus valores devueltos?

La mayoría de las funciones de Perl le dirán si funcionaron o no y se establecerán $!en falla. ¿Verificó el valor de retorno y examinó $!los mensajes de error? ¿Verificó $@si estaba usando eval?

¿Qué versión de Perl estás usando?

La última versión estable de Perl es 5.28 (o no, dependiendo de cuándo se editó por última vez). ¿Estás usando una versión anterior? Las diferentes versiones de Perl pueden tener diferentes ideas de advertencias.

¿Qué servidor web estás usando?

Diferentes servidores pueden actuar de manera diferente en la misma situación. El mismo producto de servidor puede actuar de manera diferente con diferentes configuraciones. Incluya tanta información como pueda en cualquier solicitud de ayuda.

¿Verificó la documentación del servidor?

Los programadores serios de CGI deben saber todo lo posible sobre el servidor, incluidas no solo las características y el comportamiento del servidor, sino también la configuración local. Es posible que la documentación de su servidor no esté disponible si está utilizando un producto comercial. De lo contrario, la documentación debería estar en su servidor. Si no es así, búsquelo en la web.

¿Buscaste en los archivos de comp.infosystems.www.authoring.cgi?

Esto solía ser útil, pero todos los buenos carteles han muerto o se han perdido.

Es probable que alguien haya tenido su problema antes y que alguien (posiblemente yo) lo haya respondido en este grupo de noticias. Aunque este grupo de noticias ha pasado su apogeo, la sabiduría recopilada del pasado a veces puede ser útil.

¿Puede reproducir el problema con un breve script de prueba?

En sistemas grandes, puede ser difícil rastrear un error debido a que están sucediendo muchas cosas. Intente reproducir el comportamiento problemático con el script más corto posible. Conocer el problema es la mayor parte de la solución. Sin duda, esto puede llevar mucho tiempo, pero aún no ha encontrado el problema y se están quedando sin opciones. :)

¿Decidiste ir a ver una película?

Seriamente. A veces podemos estar tan envueltos en el problema que desarrollamos un "estrechamiento perceptivo" (visión de túnel). Tomarte un descanso, tomar una taza de café o criticar a los malos en [Duke Nukem, Quake, Doom, Halo, COD] puede darte la nueva perspectiva de que necesitas para volver a abordar el problema.

¿Ha vocalizado el problema?

En serio otra vez. A veces, explicar el problema en voz alta nos lleva a nuestras propias respuestas. Habla con el pingüino (peluche) porque tus compañeros de trabajo no te escuchan. Si está interesado en esto como una herramienta de depuración seria (y la recomiendo si aún no ha encontrado el problema), también puede leer La psicología de la programación informática .

brian d foy
fuente
4
No dude en editar mi respuesta si tiene algo que agregar.
Brian D Foy
Parece que es posible que desee eliminar el enlace a las preguntas frecuentes de la meta CGI. ¿5.12.1 se considera "estable"?
Snake Plissken
1
¿Por qué no en $|=1lugar de $|++?
reinierpost
¿Por qué en $|=1lugar de $|++? Realmente no hace una diferencia, e incluso entonces, $|es mágico.
Brian D Foy
2
Buena respuesta, creo que valdría la pena mencionar que algunas de estas soluciones deberían ser puramente para la resolución de problemas y no en código de producción. use strictgeneralmente es bueno usarlo en todo momento, mientras que es fatalsToBrowserposible que no se recomiende el uso en producción, especialmente si está usando die.
vol7ron
10

Creo que CGI :: Debug también vale la pena mencionar.

Mikael S
fuente
Desafortunadamente, solo puedo editar la pregunta, no las respuestas.
Mikael S
7

¿Está utilizando un controlador de errores mientras está depurando?

dieSe imprimen declaraciones y otros errores fatales en tiempo de ejecución y tiempo de compilación STDERR, que pueden ser difíciles de encontrar y pueden confundirse con mensajes de otras páginas web de su sitio. Mientras está depurando su script, es una buena idea que los mensajes de error fatales se muestren en su navegador de alguna manera.

Una forma de hacer esto es llamar

   use CGI::Carp qw(fatalsToBrowser);

en la parte superior de su guión. Esa llamada instalará un $SIG{__DIE__}controlador (ver perlvar ) que mostrará errores fatales en su navegador, anteponiéndolo con un encabezado válido si es necesario. Otro truco de depuración de CGI que utilicé antes de oír hablar CGI::Carpfue usar evallas instalaciones DATAy __END__en el script para detectar errores en tiempo de compilación:

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

Esta técnica más detallada tiene una ligera ventaja CGI::Carpen cuanto a que detectará más errores en tiempo de compilación.

Actualización: nunca lo he usado, pero parece que CGI::Debug, como sugirió Mikael S, también es una herramienta muy útil y configurable para este propósito.

mob
fuente
3
@Ether: <DATA>es un identificador de archivo mágico que lee el script actual comenzando con __END__. Join le proporciona un contexto de lista, por lo que <fh> devuelve una matriz, una línea por elemento. Luego, unir lo vuelve a unir (uniéndolo con ''). Finalmente, eval.
derobert
@Ether: Una forma más legible de escribir la línea 2 sería:eval join(q{}, <DATA>);
derobert
@derobert: en realidad, __DATA__ es el token utilizado para iniciar la sección de datos, no __END__ (creo que esa fue mi confusión).
Éter
1
@Ether: Bueno, en realidad, ambos funcionan en el script de nivel superior (según la página de manual de perldata). Pero como se prefiere DATA , he cambiado la respuesta.
derobert
@derobert: gracias por el enlace del documento; ¡No sabía sobre el comportamiento de compatibilidad con versiones anteriores de __END__!
Ether
7

Me pregunto cómo es que nadie mencionó la PERLDB_OPTSopción llamada RemotePort; aunque es cierto que no hay muchos ejemplos de trabajo en la web ( RemotePortni siquiera se menciona en perldebug ), y fue un poco problemático para mí encontrar este, pero aquí va (es un ejemplo de Linux).

Para hacer un ejemplo adecuado, primero necesitaba algo que pudiera hacer una simulación muy simple de un servidor web CGI, preferiblemente a través de una sola línea de comando. Después de encontrar el servidor web de línea de comandos simple para ejecutar cgis. (perlmonks.org) , encontré que IO :: All - A Tiny Web Server es aplicable para esta prueba.

Aquí, trabajaré en el /tmpdirectorio; será el script CGI /tmp/test.pl(incluido a continuación). Tenga en cuenta que el IO::Allservidor solo servirá archivos ejecutables en el mismo directorio que CGI, por lo que chmod +x test.plse requiere aquí. Entonces, para hacer la ejecución de prueba CGI habitual, cambio el directorio a /tmpen la terminal y ejecuto el servidor web de una sola línea allí:

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

El comando del servidor web se bloqueará en la terminal y, de lo contrario, iniciará el servidor web localmente (en 127.0.0.1 o localhost); luego, puedo ir a un navegador web y solicitar esta dirección:

http://127.0.0.1:8080/test.pl

... y yo debería observar las prints realizados por test.plser cargado - y se muestra - en el navegador web.


Ahora, para depurar este script RemotePort, primero necesitamos un oyente en la red, a través del cual interactuaremos con el depurador de Perl; podemos usar la herramienta de línea de comandos netcat( nc, lo vi aquí: Perl 如何 ¿depuración remota? ). Entonces, primero ejecute el netcatoyente en una terminal, donde se bloqueará y esperará conexiones en el puerto 7234 (que será nuestro puerto de depuración):

$ nc -l 7234

Luego, querríamos perlcomenzar en modo de depuración con RemotePort, cuando test.plse haya llamado (incluso en modo CGI, a través del servidor). Esto, en Linux, se puede hacer usando el siguiente script "shebang wrapper", que aquí también debe estar incluido /tmpy debe hacerse ejecutable:

cd /tmp

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

chmod +x perldbgcall.sh

Esto es algo complicado - vea el script de shell - ¿Cómo puedo usar variables de entorno en mi shebang? - Stack Exchange de Unix y Linux . Pero, el truco aquí parece ser no bifurcar el perlintérprete que maneja test.pl , así que una vez que lo golpeamos, no lo hacemos exec, sino que llamamos perl"llanamente", y básicamente "fuente" nuestro test.plscript usando do(ver ¿Cómo ejecuto un ¿Secuencia de comandos Perl desde dentro de una secuencia de comandos Perl? ).

Ahora que tenemos perldbgcall.shen /tmp- podemos cambiar el test.plarchivo, para que se refiera a este archivo ejecutable en su línea shebang (en lugar del intérprete de Perl habitual) - aquí se /tmp/test.plmodifica así:

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

Ahora, ambos test.ply su nuevo manejador de shebang perldbgcall.sh, están en /tmp; y hemos ncescuchado conexiones de depuración en el puerto 7234, por lo que finalmente podemos abrir otra ventana de terminal, cambiar el directorio /tmpy ejecutar el servidor web de una sola línea (que escuchará las conexiones web en el puerto 8080) allí:

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

Una vez hecho esto, podemos ir a nuestro navegador web, y solicitar la misma dirección, http://127.0.0.1:8080/test.pl. Sin embargo, ahora, cuando el servidor web intente ejecutar el script, lo hará a través de perldbgcall.shshebang, que se iniciará perlen modo depurador remoto. Por lo tanto, la ejecución del script se detendrá y el navegador web se bloqueará, esperando los datos. Ahora podemos cambiar a la netcatterminal, y deberíamos ver el texto familiar del depurador de Perl; sin embargo, la salida es a travé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 muestra el fragmento, ahora básicamente lo usamos nccomo un "terminal", por lo que podemos escribir r(y Enter) para "ejecutar", y el script se ejecutará para hacer la declaración de punto de interrupción (consulte también En perl, ¿cuál es la diferencia entre $ DB :: single = 1 y 2? ), Antes de detenerse nuevamente (tenga en cuenta que en ese punto, el navegador aún se bloqueará).

Entonces, ahora podemos, digamos, recorrer el resto de test.pl, a través de la 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>

... sin embargo, también en este punto, el navegador se bloquea y espera los datos. Solo después de que salgamos del depurador con q:

  DB<1> q
$

... el navegador deja de bloquearse y finalmente muestra el resultado (completo) de test.pl:

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

Por supuesto, este tipo de depuración se puede realizar incluso sin ejecutar el servidor web; sin embargo, lo bueno aquí es que no tocamos el servidor web en absoluto; activamos la ejecución "de forma nativa" (para CGI) desde un navegador web, y el único cambio necesario en el propio script CGI es el cambio de shebang (y, por supuesto, la presencia del script de envoltura shebang, como archivo ejecutable en el mismo directorio).

Bueno, espero que esto ayude a alguien. Seguro que me hubiera encantado encontrarme con esto, en lugar de escribirlo yo mismo :)
. ¡Salud!

sdaau
fuente
5

Para mí, uso log4perl . Es bastante útil y 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
fuente
1

Honestamente, puedes hacer todas las cosas divertidas que aparecen en esta publicación. AUNQUE, la solución más simple y proactiva que encontré fue simplemente "imprimirlo".

En ejemplo: (código normal)

`$somecommand`;

Para ver si está haciendo lo que realmente quiero que haga: (Solución de problemas)

print "$somecommand";
Ilan Kleiman
fuente
1

Probablemente también valdrá la pena mencionar que Perl siempre le dirá en qué línea ocurre el error cuando ejecuta el script Perl desde la línea de comandos. (Una sesión SSH, por ejemplo)

Normalmente haré esto si todo lo demás falla. Conectaré SSH al servidor y ejecutaré manualmente el script Perl. Por ejemplo:

% perl myscript.cgi 

Si hay un problema, Perl se lo informará. Este método de depuración elimina cualquier problema relacionado con permisos de archivo o problemas con el navegador web o el servidor web.

gpowr
fuente
Perl no siempre le dice el número de línea donde ocurre un error. Le dice el número de línea donde se da cuenta de que algo anda mal. Es probable que el error ya haya ocurrido.
Brian D Foy
0

Puede ejecutar el script perl cgi en la terminal usando el siguiente comando

 $ perl filename.cgi

Interpreta el código y proporciona el resultado con código HTML. Informará el error si lo hubiera.

D.Karthikeyan
fuente
1
Lo sentimos, el comando $ perl -c filename.cgi valida la sintaxis del código e informa del error, si lo hay. No proporcionará el código html del CGI.
D.Karthikeyan
De perl -c filenamehecho, la invocación solo comprobará la sintaxis. Pero perl filenameimprime la salida HTML. Sin embargo, no es garantía de que no haya un error de 500 CGI, pero es una buena primera prueba.
Nagev