-+  Associazione
-+  Documenti
-+  Eventi
-+  Community
-+  Blog
-+  Link

Ottobre 2013

Dom Lun Mar Mer Gio Ven Sab
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    

Cerca






 

« Migrazione | Home | L'Olanda prende posizione sulla legge sul software »

Migliorare la navigazione sul web con Perl
27.06.04

Ai tempi in cui utilizzavo ancora Windows :-), trovavo molto utile Proxomitron, un programma che permette di filtrare le richieste del browser bloccando e/o modificando i popup, le immagini, il codice javascript e le pagine html in base a impostazioni personalizzabili in modo semplice e potente.

Proxomitron purtroppo è disponibile soltanto per Windows. Niente paura, però! Perl (e CPAN) vengono in nostro aiuto, permettendoci di realizzare in modo semplice e veloce un mini proxy, che con un po' di intraprendenza può diventare un proxomitron in miniatura. Vediamo come...

HTTP::Proxy è una distribuzione presente su CPAN che permette di realizzare un server proxy minimale in poche righe di perl.

#!/usr/bin/perl
use HTTP::Proxy;
fork and exit;
my $proxy = HTTP::Proxy->new(port => 3129);
$proxy->start();

Questo proxy non è di per sé molto utile.

Nella distribuzione di HTTP::Proxy sono presenti due classi base che permettono di estenderne il funzionamento, filtrando le richieste HTTP. Ogni filtro può lavorare a livello di headers HTTP (head), oppure a livello di contenuto effettivo (body). Rispettivamente, il filtro erediterà dalle classi base HTTP::Proxy::HeaderFilter oppure HTTP::Proxy::BodyFilter.

Una classe filtro utilizza il metodo init() per l'inizializzazione globale e può agire sulla richiesta del browser tramite il metodo filter().

Ipotizziamo ora un semplice filtro d'esempio che intercetti le richieste ad immagini di banner pubblicitari e le sostituisca automaticamente con una predefinita. Insomma, qualcosa che somigli vagamente a questo:

Come avrete già capito, questo filtro c'è già! Si tratta del modulo HTTP::Proxy::BodyFilter::Adnix. Si tratta di un proof-of-concept che mostra cosa è possibile fare con le classi filtro del framework di HTTP::Proxy. Per chi si stesse chiedendo che cosa significa Adnix, consiglio la lettura del fantastico libro Contact di Carl Sagan. In caso di curiosità estrema, una ricerca sul web può alleviare la sofferenza.

Vediamo in dettaglio la parte centrale del modulo Adnix, il metodo filter().

 1: sub filter
 2: {
 3:   my($self, $headers, $message) = @_;
 4:   my $uri = $message->uri();
 5:  
 6:   # "DECLINE" non-image urls
 7:   return 0 unless $uri =~ /\.(gif|jpe?g|png)/i;
 8:  
 9:   # Load placeholder image if not yet done
10:   if( ! $IMAGE ) {
11:     $IMAGE = $self->_loadImage();
12:   }
13:
14:   foreach( @{ $self->{_denylist} } ) {
15:     my $re = $_;
16:     if( $uri =~ $re ) {
17:       $self>proxy()>log( '', '', 'blocked ad image('.$uri.')' );
18:       my $response = HTTP::Response->new(
19:         200,
20:         'OK',
21:         HTTP::Headers->new(
22:           Content_Type => ( $self->{_image} =~ /\.(gif|png|jpg)/i
23:                                             ? qq{image/$1}
24:                                             : 'image/png' ),
25:           Content_Length => -s $IMAGE,
26:         ),
27:         $IMAGE
28:       );
29: 
30:       $self>proxy()>response($response);
31:       last;
32:     }
33:   }
34: 
35:   return 1;
36: }

Esaminiamo le parti essenziali.

 1: sub filter
 2: {
 3:   my($self, $headers, $message) = @_;

I due parametri passati al metodo filter() da HTTP::Proxy sono:

  • $headers, un reference ad un oggetto HTTP::Headers, parte della onnipresente libreria LWP, che contiene la lista degli headers della richiesta HTTP (guarda caso)
  • $message, un reference ad un oggetto HTTP::Request.
 4:   my $uri = $message->uri();
 5:  
 6:   # "DECLINE" non-image urls
 7:   return 0 unless $uri =~ /\.(gif|jpe?g|png)/i;

Alla linea 4, ad $uri viene assegnato l'URL completo della richiesta HTTP relativa all'immagine corrente.
Le linee 6 e 7 provvedono a declinare le richieste relative a URL che non siano immagini gif, jpeg o png. In realtà questo non è corretto; come sappiamo non è obbligatorio identificare le immagini con un'estensione. Per semplicità, diamo per scontato che sia così.

Con declinare (italiano impreciso per to decline), si intende ripassare la richiesta al modulo HTTP::Proxy in modo che venga servita in modo "regolare", senza l'intervento del filtro.

 9:  # Load placeholder image if not yet done
10:   if( ! $IMAGE ) {
11:     $IMAGE = $self->_loadImage();
12:   }

Le linee da 9 a 12 caricano in memoria l'immagine da utilizzare come segnaposto sostitutivo dei banner pubblicitari. A questo scopo usiamo lo scalare globale $IMAGE.

14:   foreach( @{ $self->{_denylist} } ) {
15:     my $re = $_;
16:     if( $uri =~ $re ) {

Siamo nella parte principale del filtro. Viene iniziato un ciclo su tutte le espressioni regolari definite nella denylist, che viene definita nella parte di inizializzazione come lista di espressioni regolari precompilate:

 $self->{_denylist} ||= [ map { qr($_) }
    'ad[vs\.]',
    'adv?server',
    '468x60',
    'doubleclick\.net',
    'promot[ie]',
];

Se l'URL dell'immagine corrente corrisponde ad almeno una regexp (linea 16), essa viene considerata pubblicità. Se nessuna regexp corrisponde, il filtro non viene attivato, e l'immagine viene servita normalmente.

17:       $self>proxy()>log( '', '', 'blocked ad image('.$uri.')' );
18:       my $response = HTTP::Response->new(
19:         200,
20:         'OK',
21:         HTTP::Headers->new(
22:           Content_Type => ( $self->{_image} =~ /\.(gif|png|jpg)/i
23:                                             ? qq{image/$1}
24:                                             : 'image/png' ),
25:           Content_Length => -s $IMAGE,
26:         ),
27:         $IMAGE
28:       );
29: 
30:       $self>proxy()>response($response);
31:       last;

Appurato che l'immagine corrente è da rimpiazzare, non rimane che "cortocircuitare" la richiesta HTTP in modo che non segua il ciclo normale (richiesta HTTP al server, risposta, invio al browser), ma la risposta venga generata direttamente dal nostro filtro, inserendo come dati lo stream binario dell'immagine già caricata in $IMAGE.

Di ciò si occupano le linee da 18 a 28, costruendo un nuovo oggetto HTTP::Response, che contiene anche gli header HTTP opportuni (oggetto HTTP::Headers) come Content-Type e Content-Length, in base all'immagine da servire. Questo è importante, altrimenti il browser potrebbe non interpretare in maniera corretta i dati dell'immagine servita (PNG, piuttosto che Jpeg, ...).

Ultima operazione (linea 30), segnalare al modulo HTTP::Proxy, che è già disponibile una risposta valida (oggetto HTTP::Response) che può essere inviata al browser.

Ed ecco finalmente il risultato di cotanto sforzo...

Ovviamente, il tutto è decisamente migliorabile. Si potrebbe pensare di intercettare le dimensioni delle immagini da rimpiazzare, in modo da utilizzare immagini diverse a seconda delle grandezze (tipica caratteristica dei banner pubblicitari, che si presentano per lo più in 468×60, 728×90, ...).

Facciamo ancora un passo avanti. Potremmo generare dinamicamente delle immagini segnaposto tagliate su misura in base a quelle originali. Sarebbe necessario eseguire la richiesta HTTP per leggere il contenuto delle immagini da rimpiazzare. Successivamente ricavarne le dimensioni con un modulo come Image::Info, e creare un'immagine vuota esattamente di quella grandezza, con librerie come GD oppure ImageMagick, a seconda delle esigenze.

Insomma, la strada è aperta...

Happy proxying!

Inviato da Cosimo il 27.06.04 01:55
Ti è piaciuto questo articolo? Iscriviti al feed!










Devo ricordare i dati personali?






D:
Sull'autore...
D:
La ML di Perl.it
Iscriviti! mongers@lists.perl.it è la lista ufficiale di Perl Mongers Italia per porre quesiti di tipo tecnico, per rimanere aggiornato su meeting, incontri, manifestazioni e novità su Perl.it.
D:
Annunci Google