Texto muy largo, que solo interesa el último ejemploEl problema es el siguiente:
Cuando tu marcas un tiempo de expiración con expire(), ese tiempo se marca tanto en el fichero de sesión que se guarda en la base de datos, como en el tiempo de expiración de la
cookie (galleta) que se manda al usuario.
Si el usuario accede dentro del tiempo asignado, aparecerá como registrado, pero si deja pasar ese tiempo (desde la última vez que se accedió al sistema), la galleta de su navegador habrá caducado, por lo que
no se transmitirá al cgi... Para el cgi será como si se hubiera conectado un nuevo usuario, por lo que creará una nueva sesión con new().
En resumen: nunca nos enteraremos de si la sesión ha caducado, porque el usuario nunca nos transmitirá una
cookie caducada (bueno, 'puede' darse el caso, alguna vez... depende de los relojes de cada ordenador).
Así que, por defecto, siempre recibiremos peticiones de sesiones conocidas o peticiones de nuevas sesiones. Nunca veremos que la función is_expired() sea cierta.
Este ejemplo funciona así:
Using perl Syntax Highlighting
#!/usr/bin/perl
use strict;
use warnings;
use diagnostics;
use CGI qw':standard';
use CGI::Session;
use CGI::Carp qw'fatalsToBrowser warningsToBrowser';
chdir '/home/explorer/public_html'; # directorio de trabajo
my $cgi = CGI->new; # creamos el objeto CGI
my $session # creamos el objeto CGI::Session
= CGI::Session->load(
"driver:file",
$cgi,
{Directory => './sesiones'}
);
die "ERROR: \$session indefinida\n" if not defined $session;
if ($session->is_expired) { # ¿la sesión ha expirado?
print
$session->header,
$cgi->start_html,
cabeceras_http(),
$cgi->p("Su sesión ha expirado. Identifiquese de nuevo:"),
formulario(),
$cgi->end_html,
;
terminar();
}
if ($session->is_empty) { # ¿la sesión está vacía?
$session = $session->new( # creamos una nueva
"driver:file",
$cgi,
{Directory => './sesiones'}
) or die "ERROR en la creación de nueva sesión: " .$session->errstr;
$session->expire('60'); # le dejamos estar 60 segundos
}
# vemos si se está autenticando
my $usuario = $cgi->param('login_nombre') || '';
my $passwd = $cgi->param('login_passwd') || '';
if ($usuario and $passwd and usuario_verificado($usuario, $passwd)) {
$session->param('~registrado', 1);
$session->param('nombre', $usuario); # guardamos su nombre
}
if (! $session->param('~registrado')) { # ¿es un usuario registrado?
print # No. Presentamos la página de registro
$session->header,
$cgi->start_html,
cabeceras_http(),
$cgi->p("Identifiquese:"),
formulario(),
$cgi->end_html,
;
terminar();
}
# Llegamos aquí cuando el usuario está registrado e identificado
print # sí, presentamos una página normal
$session->header,
$cgi->start_html,
cabeceras_http(),
$cgi->p('Gracias por su visita, ' . $session->param('nombre'). '. Dispone de 60 segundos.'),
$cgi->end_html,
;
## Fin del programa
terminar();
### Subrutinas
sub usuario_verificado { # consulta si el usuario es real
return $_[0] eq 'JF' and $_[1] eq 'c12';
}
sub formulario { # presenta un formulario muy simple
return
$cgi->start_form,
'Nombre: ',
$cgi->textfield('login_nombre'), br,
'Passwd: ',
$cgi->password_field('login_passwd'), br,
$cgi->submit,
$cgi->end_form,
;
}
sub cabeceras_http {
return
$cgi->p(
join $cgi->br, map { "$_ : " . $cgi->http($_) } sort $cgi->http()
)
;
}
sub terminar {
$session->flush();
CGI::Session->find('driver:file', sub {}, {Directory => './sesiones'} ); # Purgamos sesiones caducadas
exit(0);
}
Coloreado en 0.003 segundos, usando
GeSHi 1.0.8.4
Básicamente:
- Creamos el objeto CGI y el objeto CGI::Session, a partir del load()
- Si la sesión ha expirado, se le presenta un mensaje de aviso y el formulario de entrada, y termina
- Si la sesión está vacía, se crea una nueva sesión, con un tiempo de expiración de 60 segundos
- Si el usuario nos está mandando las credenciales, comprobamos su validez
- Si es un usuario valido (nos da nombre y contraseña) y verificado (le reconocemos), entonces lo marcamos con un parámetro llamado '~registrado' igual a 1 (el nombre del parámetro da igual)
- Si el usuario no está registrado, entonces le presentamos el formulario de entrada y terminamos
- Si sí lo está, le presentamos el resto del web (en este caso, una página sencilla)
- Y terminamos
Como detalles importantes:
- Este ejemplo difiere del tuyo en que estoy usando sesiones basadas en ficheros en disco, en lugar de usar una base de datos, pero el funcionamiento de la sesión es igual
- A terminar() se le llama desde distintos puntos, pues tiene varias tareas que hacer: hace un flush(), que, realmente, en este ejemplo, no es necesario, porque el propio CGI::Session lo llamaría de forma automática, al salir del programa. Caso distinto es si guardáramos la sesión en una base de datos, como es tu caso: si la variable que almacena la conexión a la base de datos sale del contexto en que fue creada, la sesión quizás no pueda grabarse bien en la base de datos. Por ello se recomienda llamar a flush() antes de terminar el programa, en un sitio seguro dentro del programa, donde estemos seguros que aún tenemos control de la $session. Y una cosa más que hace terminar(): elimina las sesiones caducadas, con la ayuda de find(). Si no lo hiciéramos, se irían acumulando indefinidamente en disco o en la base de datos.
- cabeceras_http() es una función de apoyo, para ver las cabeceras HTTP que el usuario nos manda a nuestro CGI.
En situaciones normales, esto es precisamente lo que queremos: mantener la sesión con el usuario, o pedirle que se autentifique. Así que este ejemplo vale para la gran mayoría de sitios web en los que queremos llevar un control de los usuarios que entran. Y la expiración del tiempo la da la propia expiración de la
cookie que
NO se transmite desde el usuario a nuestro cgi.
Ahora bien... este programa no hace lo tu que quieres...
Tu quieres que el programa cgi
reconozca al usuario para decirle que
su sesión ha expirado.
Eso implica que: el usuario se debe conectar a nuestro cgi con una
cookie de sesión, y que encontremos que esa sesión ha caducado en nuestro sistema.
Fíjate que lo que estás pidiendo es muy especial: pocos sitios web lo tienen implementado así... la mayoría usan la primera técnica: si la
cookie caduca, es que la sesión caduca.
Otros sitios web utilizan trucos para hacer lo que quieres.
Uno de ellos es el de transmitir dos
cookies. Una de ellas contendrá un identificador con el que relacionaremos el nombre del usuario, cuando se registre. Y la otra nos servirá para el control del tiempo de sesión.
Así, cuando el usuario lleve un tiempo sin hacer nada en nuestro sitio web, la segunda
cookie caducará y no se transmitirá, pero la primera sí se transmitirá (no caducará porque le hemos dado una fecha de expiración muy grande, como por ejemplo, 1 de abril del 2030). Entonces, ¿qué ocurre? Pues que el programa que recibe la primera
cookie sabe quién es (el usuario que se conecta), pero como no recibe la
cookie de control de tiempo, entonces sabe que la sesión ha expirado, por lo que mostrará un aviso al usuario (sabe quién es y sabe que la sesión ha caducado).
No sé si este es el comportamiento que quieres hacer o te vale con el primero, el básico. Si no quieres complicarlo, te recomiendo que te olvides de todo el tema de load() y de is_expired(). Simplemente, con hacer un new() al principio para crear la sesión, CGI::Session se dará cuenta de cuándo es una nueva sesión o de una sesión ya iniciada por el usuario. Solo te quedará hacer un expire() para marcar cuánto tiempo queremos que dure la sesión, y listo: si el usuario se conecta más allá del tiempo indicado, no transmitirá la
cookie de sesión, así que para nosotros será como si se conectara de nuevo.
¿Te vale así, o prefieres el segundo comportamiento? ¿Aquel en el que sabemos quién es, aunque haya caducado la sesión?
Si queremos que se comporte de la segunda manera, tenemos que trabajar de forma distinta:
Using perl Syntax Highlighting
#!/usr/bin/perl
use strict;
use warnings;
use diagnostics;
use CGI qw':standard';
use CGI::Cookie;
use CGI::Session;
use CGI::Carp qw'fatalsToBrowser warningsToBrowser';
my $tiempo_sesion = '10s'; # Tiempo que dura la sesión
chdir '/home/explorer/public_html'; # directorio de trabajo
my $cgi = CGI->new; # creamos el objeto CGI
my $session # creamos el objeto CGI::Session
= CGI::Session->load(
"driver:file",
$cgi,
{Directory => './sesiones'}
);
die "ERROR: \$session indefinida\n" if not defined $session;
my $cookie;
if ($session->is_expired) { # ¿la sesión ha expirado?
print
$cgi->header(),
$cgi->start_html,
cabeceras_http(),
$cgi->p("Su sesion ha expirado. Identifiquese de nuevo:"),
formulario(),
$cgi->end_html,
;
terminar();
}
if ($session->is_empty) { # ¿la sesión está vacía?
$session = $session->new( # creamos una nueva
"driver:file",
$cgi,
{Directory => './sesiones'}
) or die "ERROR en la creación de nueva sesión: " .$session->errstr;
$session->expire($tiempo_sesion); # Tiempo de expiración de la sesión
}
# Creamos una cookie con un tiempo de expiración superior al de sesión (una hora)
$cookie = $cgi->cookie( -name => $session->name, -value => $session->id, -expires => '+1h');
# vemos si se está autenticando
my $usuario = $cgi->param('login_nombre') || '';
my $passwd = $cgi->param('login_passwd') || '';
if ($usuario and $passwd and usuario_verificado($usuario, $passwd)) {
$session->param('~registrado', 1);
$session->param('nombre', $usuario); # guardamos su nombre
}
if (! $session->param('~registrado')) { # ¿es un usuario registrado?
print # No. Presentamos la página de registro
$session->header(-cookie=>$cookie),
$cgi->start_html,
cabeceras_http(),
$cgi->p("Identifiquese:"),
formulario(),
$cgi->end_html,
;
terminar();
}
# Llegamos aquí cuando el usuario está registrado e identificado
print # sí, presentamos una página normal
$session->header(-cookie=>$cookie),
$cgi->start_html,
cabeceras_http(),
$cgi->p('Gracias por su visita, ' . $session->param('nombre'). ". Dispone de $tiempo_sesion."),
$cgi->end_html,
;
## Fin del programa
terminar();
### Subrutinas
sub usuario_verificado { # consulta si el usuario es real
return $_[0] eq 'JF' and $_[1] eq 'c12';
}
sub formulario { # presenta un formulario muy simple
return
$cgi->start_form,
'Nombre: ',
$cgi->textfield('login_nombre'), br,
'Passwd: ',
$cgi->password_field('login_passwd'), br,
$cgi->submit,
$cgi->end_form,
;
}
sub cabeceras_http {
return
$cgi->p(
join $cgi->br, map { "$_ : " . $cgi->http($_) } sort $cgi->http()
)
;
}
sub terminar {
$session->flush();
CGI::Session->find('driver:file', sub {}, {Directory => './sesiones'} ); # Purgamos sesiones caducadas
exit(0);
}
Coloreado en 0.004 segundos, usando
GeSHi 1.0.8.4
Esta versión es casi idéntica a la anterior, pero hay unos pequeños cambios. Estos son los más importantes:
- Línea 7: importamos el módulo CGI::Cookie
- Línea 26: declaramos la variable global $cookie
- Línea 51: creamos una cookie, usando el mismo nombre y el mismo id de sesión que el que utiliza CGI::Session, pero con la diferencia de que esta cookie tiene un tiempo de expiración mucho más largo: una hora
- Línea 65 y 78: a la hora de enviar las cabeceras, enviamos nuestra propia $cookie. En realidad, lo que estamos haciendo es "machacar" la cookie que crea CGI::Session, con un tiempo de expiración marcado en la línea 48, por nuestra propia $cookie, con un tiempo de expiración de 1 h.
El resultado es el siguiente: el usuario se registra de forma normal. La
cookie almacenada en su equipo expirará en una hora, pero la sesión, en el servidor, expirará mucho antes (10 s como ves en la línea 11). Si el usuario deja pasar esos diez segundos y vuelve a conectarse a nuestro cgi, su navegador transmitirá la
cookie (no ha pasado una hora, por lo que no ha caducado). CGI::Session verá (en el load() de la línea 18) que
sí hay una sesión correspondiente a la
cookie que está recibiendo, pero, como han pasado más de diez segundos, la marca como expirada, y
elimina la sesión (en el ejemplo, al ser ficheros, lo que hace es borrar el fichero de sesión). El programa entra por el if() de la línea 29 (is_expired() da verdadero), pero
toda la información de la sesión se ha perdido (todos los parámetros almacenados en la sesión). Sale el aviso de que ha expirado y que se registre de nuevo.
Si el usuario, en vez de registrarse, recarga la página, entonces load() ya no encontrará una sesión correspondiente a la
cookie que está recibiendo, porque borró la sesión hace un momento. Entonces, lo que ve el usuario es el mensaje directo de "Identifíquese", como si nunca hubiera entrado.
Bueno, esta es una forma de hacerlo, que se aproxima a lo que quieres hacer, pero no deja de ser una pequeña chapuza: estamos sobreescribiendo una
cookie que ha fabricado CGI::Session por una fabricada por nosotros. Esto podría no funcionar si el autor de CGI::Session hace algún cambio importante en el código.
Hay una solución mejor: hacer que sea un parámetro de la sesión el que lleve el control del tiempo de expiración:
Using perl Syntax Highlighting
#!/usr/bin/perl
use strict;
use warnings;
use diagnostics;
use CGI qw':standard';
use CGI::Session;
use CGI::Carp qw'fatalsToBrowser warningsToBrowser';
my $tiempo_sesion = '+1d'; # Tiempo que dura la sesión (máximo)
my $tiempo_login = '+10s'; # Tiempo que dura la sesión (identificado)
chdir '/home/explorer/public_html'; # directorio de trabajo
my $cgi = CGI->new; # creamos el objeto CGI
my $session # creamos el objeto CGI::Session
= CGI::Session->new('driver:file', $cgi, {Directory => './sesiones'});
die "ERROR: \$session indefinida\n" if not defined $session;
if ($session->is_new) { # ¿es una nueva sesión?
$session->expire($tiempo_sesion); # Tiempo de expiración de la sesión (todo el día)
print # Presentamos la página de registro
$session->header,
$cgi->start_html,
cabeceras_http(),
$cgi->p("Identifiquese:"),
formulario(),
$cgi->end_html,
;
terminar();
}
my $usuario = $cgi->param('login_nombre') || ''; # Vemos si se está registrando
my $passwd = $cgi->param('login_passwd') || '';
if ($usuario and $passwd and usuario_verificado($usuario, $passwd)) {
$session->param('~registrado', 1); # Lo marcamos como registrado
$session->expire('~registrado', $tiempo_login); # Tiempo que dura la sesión (login)
$session->param('nombre', $usuario); # Guardamos su nombre
}
if (! $session->param('~registrado')) { # ¿Ha pasado el tiempo de sesión login?
my $usuario = $session->param('nombre') || ''; # Recuperamos su nombre
$usuario = ", $usuario" if $usuario;
print
$cgi->header(),
$cgi->start_html,
cabeceras_http(),
$cgi->p("Su sesion ha expirado$usuario. Identifiquese de nuevo:"),
formulario(),
$cgi->end_html,
;
terminar();
}
# Llegamos aquí cuando el usuario está registrado e identificado
print # presentamos una página normal
$session->header,
$cgi->start_html,
cabeceras_http(),
$cgi->p('Gracias por su visita, ' . $session->param('nombre'). ". Dispone de $tiempo_login mas."),
$cgi->end_html,
;
## Fin del programa
terminar();
### Subrutinas
sub usuario_verificado { # consulta si el usuario es real
return $_[0] eq 'JF' and $_[1] eq 'c12';
}
sub formulario { # presenta un formulario muy simple
return
$cgi->start_form,
'Nombre: ',
$cgi->textfield('login_nombre'), br,
'Passwd: ',
$cgi->password_field('login_passwd'), br,
$cgi->submit,
$cgi->end_form,
;
}
sub cabeceras_http {
return
$cgi->p(
join $cgi->br, map { "$_ : " . $cgi->http($_) } sort $cgi->http()
)
;
}
sub terminar {
$session->flush();
CGI::Session->find('driver:file', sub {}, {Directory => './sesiones'} ); # Purgamos sesiones caducadas
exit(0);
}
Coloreado en 0.005 segundos, usando
GeSHi 1.0.8.4
Incluso queda más corto
Los cambios más importantes son:
- Línea 18. Quitamos load() y lo cambiamos a new(). Él se encargara, o bien de cargar una sesión anterior, o de crear una nueva
- Línea 22. Si la sesión es nueva, pues le damos un tiempo de expiración muy largo (todo el día). Este es el valor de expiración tanto para la sesión como para la cookie que se envía al usuario (y que nos devuelve). Le presentamos el formulario de registro al usuario y terminamos
- Línea 42. (el truco) Le damos al parámetro '~registrado' un valor de 1 (podría ser otro valor mejor), y un tiempo de expiración de $tiempo_login. Este es el tiempo que queremos que esté activa la sesión de usuario sin que tenga que identificarse otra vez
- Línea 47. Preguntamos si está registrado o no. '~registrado' indicará si, además de estar identificado en el sistema, ha caducado o no la sesión. Su no-presencia indicará que ha caducado, por lo que le avisamos al usuario, y terminamos
El resto es igual a lo que teníamos antes.
Espero que ahora haya quedado claro.
Falta la parte de borrar la sesión por indicación del usuario (delete()), pero es cuestión de poner otro if() en el lugar adecuando (por ejemplo, antes de ver si se está registrando).