Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Introduzione a Rack

Implemetare uno stack di middleware per gestire efficacemente richieste e risposte HTTP
Implemetare uno stack di middleware per gestire efficacemente richieste e risposte HTTP
Link copiato negli appunti

Rack è una gemma Ruby nata cercando di dare una risposta al sentito bisogno di standardizzazione nelle comunicazioni tra i vari middleware che vogliono concorrere alla gestione della richieste e alla produzione delle risposte di una chiamata HTTP.

La strategia che implementa è incredibilmente semplice ed efficace: Rack consente di concatenare in modo sequenziale i vari middleware tra loro.

Ogni richiesta in arrivo dal webserver viene quindi strutturata all'interno di un oggetto specifico e passata al primo anello della catena il quale ha in carico la responsabilità di passarlo a sua volta al secondo anello, e così via.

Prima di ogni passaggio tale oggetto può essere chiaramente arricchito di informazioni prodotte dal middleware. Una volta completato il percorso fino alla fine della catena l'algoritmo inverte direzione e ripercorre tutti gli anelli in senso inverso dando la possibilità ad ognuno di essi di poter incidere a proprio piacimento nella creazione della risposta. Al termine di questo ulteriore passaggio la risposta viene passata al server per poi essere inviata al browser.

La comodità di questo semplice meccanismo è evidente: qualunque libreria che aspira a middleware, se scritta secondo alcune semplici linee guida, può divenire rack-enabled e quindi essere inserita all'interno di queste catene beneficiando della combinazione con gli altri anelli.

Questo articolo vuole offrire sia una panoramica sulla tecnica di creazione di un middleware rack-enabled sia un buono spunto su come creare ed utilizzare una catena di middleware utilizzando Rack; entrambi i temi verranno esposti nel seguito dell'articolo.

Un middleware rack-enabled

Creare un middleware rack-enabled è un'operazione estremamente semplice, la classe in questione deve solamente poter ricevere un parametro nel costruttore e possedere un metodo chiamato call che accetta in ingresso un ulteriore parametro, un semplice esempio di questa struttura è il seguente:

class Reverse
  
  def initialize(app)
    @app = app
  end
  
  def call(env)
    @app.call(env)
  end
  
end

Il metodo call è la chiave di volta che consente ai vari anelli della catena, alle varie classi dei vari middleware, di potersi passare e di poter modificare l'oggetto contenente i dati di richiesta HTTP. Nell'esempio appena esposto, tale oggetto è contenuto nell'argomento env mentre il parametro app, passato al costruttore della classe, contiene un'istanza del successivo anello della catena.

È piena responsabilità di ogni anello invocare correttamente il successivo; nel nostro caso il compito viene assolto dall'unica istruzione presente nel metodo call. Volendo provare a schematizzare l'operatività in termini di chiamate a funzioni per una catena formata da tre ipotetici middleware Compress, Reverse e Traslate ne risulterebbe il seguente listato:

risposta_in_uscita = Compress.new(
                       Reverse.new(
                         Traslate.new(
                           MyApp.new
                         )
                       )
                     ).call(richiesta_in_ingresso)			 

Questo esempio offre lo spunto per riflettere sulla considerazione che di ogni catena l'ultimo anello è sempre formato dall'applicazione stessa, è quindi diretta conseguenza di questo ragionamento l'osservazione che ogni applicazione che voglia beneficiare di uno stack costruito con middleware rack-enabled debba predisporre al suo interno una classe che contenga un metodo call che deve fungere da 'punto di connessione' ricevendo in ingresso l'oggetto contenente le richieste e ritornando le debite risposte (ad esempio una pagina HTML).

Completiamo la creazione del middleware Reverse aggiungendo quel minimo di logica necessaria a far si che ogni frammento di testo presente nella oggetto contenente la risposta venga invertito:

class Reverse
  
  def initialize(app)
    @app = app
  end
  
  def call(env)
     status, head, body = @app.call(env)
    [status, head, body.gsub(/>([^<]+)</){|m| ">#{$1.reverse}<"}]
  end
  
end

L'oggetto che contiene la risposta è per convenzione costruito utilizzando un semplice array di tre valori, rispettivamente lo stato della risposta HTTP (ad es: 200), gli header della risposta ed il body della stessa. Nel middleware in costruzione è sufficiente quindi ritornare gli stessi tre elementi ricevuti avendo premura di invertire qualsiasi frammento di testo presente nel body.

E ora Rackup!

Il Reverse middleware è pronto, ora va testato e provato. Per fare questo esistono due alternative: creare una piccola applicazione che abbia tra i suoi middleware Reverse o inserire questa classe all'interno di uno stack già esistente, magari all'interno di un'applicazione Rails.

Proviamo entrambe le alternative; per la prima l'approccio più consigliato è quello di utilizzare rackup, una piccola utility a linea di comando capace di esporre un'applicazione ed il suo stack di middleware attraverso un webserver (tipicamente Mongrel). Cuore nevralgico di questa installazione è un file di configurazione, spesso chiamato config.ru, che specifica che middleware e che applicazione debbano fare parte di ogni progetto.

Il file config.ru, che va posizionato per comodità nella stessa cartella che contiene la classe Reverse, contiene puro e semplice codice Ruby, ad esempio:

# -p 3000 

app = proc do |env|
  [ 200, {'Content-Type' => 'text/html'}, <<-EOS ]
  
    <html>
    <body style="color:white;text-shadow: black 0px 0px 5px;font-size: 18px;">
    <pre style=";margin:20px;">
        Rack provides a minimal, modular and adaptable interface for 
        developing web applications in Ruby. By wrapping HTTP requests 
        and responses in the simplest way possible, it unifies and 
        distills the API for web servers, web frameworks, and software 
        in between (the so-called middleware) into a single method call.

                              (dalla definizione di Rack, wikipedia.org)
    <pre>
    </body>
    </html>
    
  EOS

end

run app

Nel listato possiamo distinguere due istruzioni. La prima memorizza in app una funzione anonima che accetta in ingresso una variabile e ritorna un array di tre elementi: stato, headers e body. Volendo legare questo con quanto detto fino ad ora potremmo assentire sul fatto che app rispetta tutti i canoni di un applicazione rack-enabled, infatti risponde in modo corretto al metodo app.call(env). La seconda istruzione invece esegue app.

Aprendo un terminale all'interno della stessa cartella del file config.ru ed eseguendo da linea di comando l'utility rackup senza alcun parametro aggiuntivo verrà lanciato un webserver sulla porta 3000 che grazie a rack convoglierà ogni richiesta nell'applicazione appena illustrata.

Provando a puntare un browser all'indirizzo localhost:3000 sarà possibile leggere la definizione di Rack della wikipedia con una ombreggiatura tipicamente CSS3.

Il file di configurazione config.ru appena presentato non contiene alcun middleware (se non quelli che implicitamente Rack include), per aggiungere alla catena l'anello Reverse è necessario modificare l'inizio del file segue:

require 'reverse'
# -p 3000 
use Reverse

#... resto del file come da esempio precedente...

Fermando (CTRL+C) e riavviando il server la pagina all'indirizzo localhost:3000 mostrerà ora la stessa definizione di prima ma completamente invertita: merito di Reverse!

Rack on Rails

Rails utilizza in modo esteso Rack; a testimonianza di ciò per la versione 2.3.x esiste un file denominato middlewares.rb all'interno della gemma actionpack che elenca i middleware implementati da Rails:

use "Rack::Lock", :if => lambda {
  !ActionController::Base.allow_concurrency
}

use "ActionController::Failsafe"

use lambda { ActionController::Base.session_store },
    lambda { ActionController::Base.session_options }

use "ActionController::ParamsParser"
use "Rack::MethodOverride"
use "Rack::Head"

use "ActionController::StringCoercion"

Rails fornisce inoltre un set di API con le quali è possibile aggiungere e rimuovere anelli a questa catena; ad esempio se si volesse agganciare la classe Reverse si potrebbe inserire all'interno del file environment.rb dell'applicazione (nel blocco Rails::Initializer.run do |config|):

 config.middleware.use Reverse

Implementando questa piccola modifica nel file di configurazione di un applicazione Rails già esistente ogni pagina HTML prodotta da tale progetto verrebbe presentata al browser con ogni contenuto testuale invertito.

Metal

Naturalmente è possibile fare cose ben più interessanti con Rack e Rails, un esempio concreto di tali possibilità è offerto da Metal. Metal è una classe che implementa un meccanismo di funzionamento interessante: a fronte della ricezione di una richiesta discerne se tale richiesta può essere gestita in autonomia o passata alla restante porzione di stack. Nel primo caso lo stesso Metal genera una tripletta di risposta ed interrompe la chiamata all'anello successivo della catena; visto che stiamo parlando di Rails questo significa che per la richiesta in questione non verranno istanziati ne controller ne view, con relativo sensibile beneficio in termini di velocità risposta. Nel secondo caso invece Metal agisce come connettore trasparente passando l'intero environment all'anello successivo.

Creare un middleware Metal-based in un applicazione Rails è impegnativo quanto digitare dalla root dell'applicazione:

 ruby script/generate metal HelloWorld

Il file creato dal generator (/app/metal/hello_world.rb) è sufficientemente autoesplicativo:

# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class HelloWorld
  def self.call(env)
    if env["PATH_INFO"] =~ /^/hello_world/
      [200, {"Content-Type" => "text/html"}, ["Hello, World!"]]
    else
      [404, {"Content-Type" => "text/html"}, ["Not Found"]]
    end
  end
end

Se l'URL di navigazione risponde positivamente al confronto con l'espressione regolare allora lo stack viene interrotto e la classe HelloWorld si prende la responsabilità di costruire una tripletta (in questo caso molto semplice) da passare al browser.

In caso contrario invece viene creata una tripletta convenzionale di diniego che viene poi 'tradotta' da Rails nell'istruzione @app.call(env) che abbiamo visto in precedenza passando di fatto il controllo al successivo middleware della catena.

Conclusioni

Rack è potentissimo, il suo sistema ha come punto di forza l'estrema semplicità della sua implementazione: qualsiasi middleware può beneficiare dello stack offerto da Rack con una quantità minima di sforzo. Il suo utilizzo comporta inoltre un piacevole incapsulamento dei singoli aspetti del progetto che lo incorpora e aiuta lo sviluppatore ad implementare in modo saggio e ordinato le singole componenti di quanto sta realizzando. Infine la possibilità di creare una propria catena di middleware con la stessa facilità con la quale si scrive una lista ordinata rende ancora più evidente la sottile eleganza di questa ormai irrinunciabile libreria.


Ti consigliamo anche