Introduzione al Perl Object Environment (POE)/2

L’articolo che segue è la seconda e ultima parte di questa introduzione al Perl Object Environment. La prima parte è disponibile a questo link.

Wheel e componenti

La prima classe di oggetti che possono aiutare a sviluppare più rapidamente le nostre applicazioni con POE sono chiamate Wheel. Si tratta di insiemi di event handler, che possono essere iniettati all’interno di una sessione per istruirla a reagire ad un più ampio insieme di stimoli senza dover faticare troppo. Un esempio semplice da seguire é quello che adopera la classe POE::Wheel::FollowTail. Si tratta di una wheel che aiuta a creare applicazioni che, come il comando UNIX tail, seguono l’evolversi di un file specificato.

Ecco un esempio minimale che ne illustra l’uso:

use strict;
use warnings;

use POE qw/ Wheel::FollowTail /;

POE::Session->create(
	inline_states => {
		_start => sub {

			# Setup wheel
			$_[ HEAP ]->{ tailer } = POE::Wheel::FollowTail->new(
				Filename   => '/var/log/system.log',
				InputEvent => 'got_line'
			)
		},
		got_line => sub {
			my $line = $_[ARG0];
			print "$line\n";
		}
	}
);

POE::Kernel->run();

Da notare l’uso di use POE qw/ Wheel::FollowTail /;, per indicare che vogliamo adoperare elementi aggiuntivi del framework. Inoltre, è importante osservare che abbiamo mantenuto un riferimento alla wheel all’interno dello heap: se non lo avessimo fatto l’istanza di POE::Wheel::FollowTail sarebbe stata automaticamente distrutta, e gli eventi cui siamo interessati non sarebbero mai stati generati. Per il resto, il codice non dovrebbe presentare misteri: l’istanziazione dell’oggetto di classe POE::Wheel::FollowTail specifica il file che desideriamo seguire, unitamente all’event handler che vogliamo sia usato quando una nuova linea viene aggiunta al file: per specificarlo, abbiamo legato a InputEvent il nome dell’event handler che vogliamo sia richiamato. L’event handler in questione non presenta invece novità: sapevamo già, infatti, come si ricevono parametri dal mittente dell’evento.

Una volta lanciato il programma, esso continuerà con cadenza regolare (se volete potete agire su questa cadenza, usando il parametro PollInterval) a controllare se altre linee sono state aggiunte al file. Ogni volta che questo accade, verrà inviato un messaggio alla sessione che contiene la wheel, scatenando così l’evento legato al parametro InputEvent.

Notiamo infine che l’uso di una wheel nella nostra sessione non ne ha stravolto in alcun modo la struttura: vuol dire che possiamo già sfruttare la potenza delle wheel con le nozioni apprese sin qui. Pensando all’esempio precedente, potremmo immaginare di costruire una sveglia intelligente che interrompe il sonno di Bob, Carl e compagnia quando qualche evento critico viene scritto nei file di log. Oppure, e questa forse è un’idea più interessante, potremmo scrivere un tail evoluto che, anzichè limitarsi a mostrare le righe di un file di log, ne fa il parsing e aggiorna in tempo reale le statistiche d’uso di un determinato sistema.

Invito a consultare la documentazione di altre wheel di uso tipico, come POE::Wheel::Run, che aiuta ad interagire con un programma esterno, oppure POE::Wheel::ReadLine, che permette di aggiungere facilmente alla nostra applicazione una interfaccia da riga di comando.

Componenti

Gli ultimi esemplari importanti nella fauna di POE sono i componenti. Nella tassonomia di POE, sono considerabili come gli oggetti più evoluti: si tratta infatti di classi che implementano completamente sessioni pronte all’uso.

Visto che all’inizio dell’articolo abbiamo detto che POE è particolarmente versato per il networking, non sembra fuori tema illustrare uno dei componenti più divertenti da usare: POE::Component::IRC. È questo un oggetto che permette facilmente di scrivere software che si avvalgono del protocollo IRC, come ad esempio bot.

Il modello è quello già esplorato nell’esempio della sveglia. Avremo una sessione specializzata nel reagire agli eventi del protocollo di IRC; in più, tale sessione farà da broadcaster: dovremo quindi configurare un’altra sessione per reagire agli eventi scatenati da POE::Component::IRC. Tali reazioni specifiche costituiranno il corpo della nostra applicazione. O, se preferite, la personalità del bot che stiamo costruendo.

Per cominciare, carichiamo POE specificando che vogliamo usare il componente POE::Component::IRC:

use POE qw/ Component::IRC /;

Ora definiamo la configurazione del bot (il nickname e ircname con cui si presenterà in rete, il server e la porta). Inoltre, a parte, il canale IRC nel quale vogliamo entrare (raccomando di effettuare le vostre prove su un canale di test, per non disturbare altri utenti):

my %configuration = (
	nick    => 'test',
	ircname => 'test',
	server  => 'irc.freenode.net',
	port    => 6667
);

my $channel = '#test_poe';

A questo punto, siamo pronti per istanziare il componente. Notate che il costruttore si chiama spawn, ad indicare che stiamo creando un nuovo processo: nel modello di POE, una nuova sessione.

my $irc = POE::Component::IRC->spawn( %configuration )
  or die "Something has gone wrong: $!";

L’oggetto $irc così creato è a questo punto pronto per connettersi al server IRC, e ad inoltrare eventi a chi ne farà richiesta. Provvediamo quindi a creare e configurare la sessione che farà da listener per questi eventi. Per il momento omettiamo il corpo degli event handler, per mostrare la visione d’insieme della sessione.

POE::Session->create(
	inline_states => {
		_start     => ...
		irc_001    => ...
		_default   => ...
	},
	heap => { irc => $irc },
);

A proposito dell’evento _start sappiamo già tutto: sarà il primo ad essere inviato alla nostra sessione, e si occuperà principalmente di dire al componente di collegarsi al server. irc_001 è il segnale che verrà inoltrato dal componente alla nostra sessione, quando il server risponderà al suo tentativo di connessione. Infine, _default è un catch-all da richiamare all’occorrenza di eventi non specificamente trattati. Altro particolare da notare è l’inizializzazione dello heap, nel quale abbiamo memorizzato un riferimento al componente: servirà molto presto, quando saremo noi a dovergli inviare degli eventi.

Vediamo ora il corpo di ciascuno degli handler introdotti.

_start => sub {
	my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];

	my $irc_session = $heap->{irc}->session_id();
	$kernel->post( $irc_session => register => 'all' );
	$kernel->post( $irc_session => connect => {} );
	undef;
},

Per prima cosa, per comodità memorizziamo in una variabile di appoggio, $irc_session, l’ID della sessione corrispondente al componente. Si tratta di una ulteriore tecnica per rivolgerci ad una sessione. Attraverso questo riferimento, spediamo due eventi al componente. Il primo (register) è fondamentale: stiamo comunicando al componente che vogliamo diventare listener di tutti ('all') gli eventi che esso è in grado di mandarci. Inviando il secondo evento (connect), invece, comunichiamo al componente che vogliamo che finalmente si connetta al server.

irc_001 => sub {
	my ( $kernel, $sender ) = @_[ KERNEL, SENDER ];

	# Ciascuno degli eventi irc_* avrà come mittente 
	# la sessione POE::Component::IRC
	
	$kernel->post( $sender => join => $channel );
	undef;
},

L’evento irc_001 viene scatenato quando il server IRC invia il suo welcome message. Poichè in precedenza ci siamo qualificati come listener di tutti gli eventi scatenati dal componente, riceveremo anche questo. Lo scopo è dare ulteriori istruzioni al componente stesso quando avrà terminato di connettersi al server IRC. In questo caso, gli spediamo un messaggio che gli indicherà di connettersi al canale precedentemente configurato.

A questo punto, se a vostra volta vi sarete connessi al canale configurato nell’applicazione, vedrete il bot entrare. Potete stappare una bottiglia.

Il nostro piccolo esempio non pretende di fare molto di più. Banalmente, farà il log di tutti gli eventi inviati dal componente. Potete comunque usare tale handler per studiare il protocollo IRC, e per arricchire di funzionalità il vostro bot.

_default => sub {
	my ( $event, $args ) = @_[ ARG0 .. $#_ ];
	my @output = ("$event: ");

	foreach my $arg (@$args) {
		if ( ref($arg) eq 'ARRAY' ) {
			push( @output, "[" . join( " ,", @$arg ) . "]" );
		}
		else {
			push( @output, "'$arg'" );
		}
	}
	print STDOUT join ' ', @output, "\n";
	return 0;
}

In questa procedura abbiamo usato la scrittura $#_, forse non familiare ai programmatori Perl meno esperti: essa denota l’indice dell’ultimo elemento dell’array @_. Scrivendo @_[ ARG0 .. $#_ ] vogliamo dunque riferirci all’intero insieme di parametri passati all’event handler.

Ecco il sorgente completo dell’esempio:

#!/usr/bin/perl 

use strict;
use warnings;

use POE qw/ Component::IRC /;

my %configuration = (
	nick    => 'test',
	ircname => 'test',
	server  => 'irc.freenode.net',
	port    => 6667
);

my $channel = '#test_poe';

my $irc = POE::Component::IRC->spawn( %configuration )
  or die "Something has gone wrong: $!";

POE::Session->create(
	inline_states => {
		_start => sub {
			my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];

			my $irc_session = $heap->{irc}->session_id();
			$kernel->post( $irc_session => register => 'all' );
			$kernel->post( $irc_session => connect => {} );
			undef;
		},
		irc_001 => sub {
			my ( $kernel, $sender ) = @_[ KERNEL, SENDER ];

			# In any irc_* events SENDER will be the PoCo-IRC session
			$kernel->post( $sender => join => $channel );
			undef;
		},
		_default => sub {
			my ( $event, $args ) = @_[ ARG0 .. $#_ ];
			my @output = ("$event: ");

			foreach my $arg (@$args) {
				if ( ref($arg) eq 'ARRAY' ) {
					push( @output, "[" . join( " ,", @$arg ) . "]" );
				}
				else {
					push( @output, "'$arg'" );
				}
			}
			print STDOUT join ' ', @output, "\n";
			return 0;
		}
	},
	heap => { irc => $irc },
);

$poe_kernel->run();

exit 0;

Conclusioni

In un articolo introduttivo si può pretendere solamente di scalfire la superficie di uno strumento così versatile. Come già detto, invito a consultare la documentazione delle classi descritte fin qui: ci sono un sacco di cose in più da scoprire. Se volete trovare esempi pratici, poe.perl.org è un’ottima risorsa. Per i dubbi, esiste una mailing list e un canale IRC. Se preferite esprimervi in Italiano, Perl.it è a disposizione.

Policy per i commenti: Apprezzo moltissimo i vostri commenti, critiche incluse. Per evitare spam e troll, e far rimanere il discorso civile, i commenti sono moderati e prontamente approvati poco dopo il loro invio.