POE es un framework escrito en Perl. POE permite escribir programas que manejan entradas desde múltiples fuentes asíncronas. Éstas son grandes palabras, pero sólo significa que obtendrás la información cuando esté disponible, y no tendrás que preocuparte por quedarte esperando.
La cooperación se logra creando un conjunto de estados, los cuales son invocados por eventos. Los eventos son generados por los motores de entradas (llamados aquí Wheels), por los timers o por otros estados.
En el corazón de POE se encuentra el POE kernel. Mantiene una cola de eventos programados y utiliza la funcionalidad select o poll del Sistema Operativo para ver la actividad de los sockets o manejadores de archivos que te interesan. Cuando sea el tiempo de disparar un evento, o hay datos disponibles, el manejador del estado asociado es invocado. Otros event loops están también disponibles, haciendo posible tener programas POE que tengan interfaces de usuario Tk o curses, por ejemplo.
Ejemplo: cómo hacer una entrada de datos con timeout
En este tutorial veremos cómo ponerle un timeout a la STDIN. Veremos que es fácil escribir aplicaciones interactivas en POE con edición en la línea de comandos y con historial.
El primer paso en cualquier programa POE es usar el módulo POE. Como los programas POE a menudo necesitan usar varios módulos del namespace POE::, tú puedes hacer
use POE qw/Wheel::ReadLine Component::Client::HTTP::SSL/;
como un atajo para
use POE;
use POE::Wheel::ReadLine;
use POE::Component::Client::HTTP::SSL;
En nuestro ejemplo sólo necesitamos POE::Wheel::ReadLine, el cual maneja nuestros requerimientos de entrada.
Cada programa consiste en una o más sesiones POE, las cuales mantienen un conjunto de estados.
use strict;
use POE qw/Wheel::ReadLine/;
$|++;
POE::Session->create(
inline_states =>
{ _start => \&handler_start,
gotLine => \&handler_gotLine,
prompt => \&handler_prompt,
timeout => \&handler_timeout,
}
);
En el constructor de la sesión especificamos un hash de nombres de estados, y los manejadores asociados a esos estados. Las subrutinas pueden tener nombre, como aquí, o ser referencias a subrutinas anónimas.
Los estados _start y _stop son especiales, ellos son invocados por el kernel cuando la sesión es creada, o justo antes de ser destruída.
En este caso no necesitamos hacer nada especial para manejar el _stop, así que este estado es comentado y su manejador no es implementado.
El manejador del _start será invocado antes que POE::Session->create() retorne.
El próximo paso es hacer andar el kernel, y salir del programa una vez hecho esto.
$poe_kernel->run();
exit;
$poe_kernel es exportado por POE automáticamente.
Manejadores
De acuerdo, no hemos definido ningún manejador todavía, así que nuestro programa aún no compila, ni siquiera corre.
Cada manejador recibirá un largo número de argumentos en @_. Los más interesantes son el heap, el kernel y el session asociados con el estado.
El heap es solo un escalar, casi siempre una referencia a hash.
Éstos valores pueden ser accedidos usando un array slice sobre @_ para inicializar variables, o explícitamente referenciadas por $_[HEAP], $_[KERNEL], $_[SESSION]. POE exporta HEAP, KERNEL, SESSION y otras constantes varias en forma automática.
El primer manejador que veremos es nuestro manejador de start:
sub handler_start {
my ($kernel, $heap, $session) = @_[KERNEL, HEAP, SESSION];
$heap->{wheel} = POE::Wheel::ReadLine->new
(
InputEvent => 'gotLine',
);
$kernel->yield('prompt');
}
Wheels
Los módulos POE::Wheel son los que manejan el pegamento molesto de nuestros generadores externos de eventos, tales como sockets o manejadores de archivo, hacia los estados POE.
POE::Wheel::ReadLine es el que invoca estados cuando los datos son entrados por la consola. También maneja la edición y el historial en la línea de comandos, con un poco de ayuda de nosotros.
Note que salvamos el wheel en nuestro hash %{$heap}. De otro modo el wheel sería inmediatamente destruído, desde que no habría referencias hacia él. Usaremos este truco de nuevo más tarde, cuando sea tiempo de salir. Por ahora asociamos el InputEvent del wheel con el estado 'gotLine' (manejado por la subrutina _gotLine). Entonces usamos el "yield" para pedirle al kernel que ejecute el estado prompt lo antes posible.
sub handler_prompt {
my ($kernel, $heap, $session) = @_[KERNEL, HEAP, SESSION];
print "You have 10 seconds to enter something, or I'll quit!$/";
$heap->{wheel}->get('Type fast: ');
$kernel->delay('timeout',10);
}
En este manejador usamos el método get en nuestro wheel para pedirle una entrada al usuario y programamos un timeout de 10 segundos.
Aún si no es la primera vez que el manejador es invocado, la llamada a delay() elimina el evento anterior y programa uno nuevo de 10 segundos.
A partir de aquí las cosas están en manos del kernel. Si el usuario no hace nada en 10 segundos (más o menos, los tiempos de espera son aproximados) el estado de timeout será activado.
sub handler_timeout {
my ($kernel, $heap, $session) = @_[KERNEL, HEAP, SESSION];
print "You took too long, game over$/";
$heap->{wheel}=undef;
}
Cuando el atributo wheel es puesto undef en la subrutina handler_timeout, el wheel es destruído, y dado que no hay eventos pendientes ni otras fuentes de eventos, el kernel termina.
Si el usuario entra algo por consola o presiona Ctrl-C, el manejador gotLine es llamado.
sub handler_gotLine {
my ($kernel, $heap, $session, $arg, $exception) =
@_[KERNEL, HEAP, SESSION, ARG0, ARG1];
if(defined $arg) {
$heap->{wheel}->addhistory($arg);
print "Very good, you entered '$arg'. You get another 10 seconds.$/";
}
else {
print "Got input exception '$exception'. Exiting...$/";
$heap->{wheel}=undef;
$kernel->delay('timeout');
return;
}
$kernel->yield('prompt');
}
Una cosa interesante aquí es que leemos ARG0 y ARG1 de @_. POE pasa argumentos en @_ en el rango ARG0 hasta $#_. En el caso de un InputHandler para este wheel, ARG0 será el texto de la entrada, y si es undef, ARG1 será el código de excepción.
Después de manejar la entrada, este manejador lleva de nuevo al manejador 'prompt', el cual reprograma el timeout y pide datos al usuario de nuevo.