Che cos'è il middleware Rack in Ruby? Non sono riuscito a trovare una buona spiegazione del significato di "middleware".
Che cos'è il middleware Rack in Ruby? Non sono riuscito a trovare una buona spiegazione del significato di "middleware".
Risposte:
Il middleware Rack è più che "un modo per filtrare una richiesta e una risposta" - è un'implementazione del modello di progettazione della pipeline per i server Web che utilizzano Rack .
Separa in modo molto chiaro le diverse fasi dell'elaborazione di una richiesta: la separazione delle preoccupazioni è un obiettivo chiave di tutti i prodotti software ben progettati.
Ad esempio con Rack I posso avere fasi separate della pipeline facendo:
Autenticazione : quando arriva la richiesta, i dettagli di accesso degli utenti sono corretti? Come convalidare questo OAuth, autenticazione di base HTTP, nome / password?
Autorizzazione : "l'utente è autorizzato a svolgere questo specifico compito?", Ovvero la sicurezza basata sui ruoli.
Memorizzazione nella cache : ho già elaborato questa richiesta, posso restituire un risultato memorizzato nella cache?
Decorazione : come posso migliorare la richiesta per migliorare l'elaborazione a valle?
Monitoraggio delle prestazioni e dell'utilizzo : quali statistiche posso ottenere dalla richiesta e dalla risposta?
Esecuzione : gestisce effettivamente la richiesta e fornisce una risposta.
Essere in grado di separare le diverse fasi (e facoltativamente includerle) è di grande aiuto nello sviluppo di applicazioni ben strutturate.
C'è anche un ottimo ecosistema che si sviluppa attorno a Rack Middleware: dovresti essere in grado di trovare componenti rack predefiniti per eseguire tutti i passaggi sopra e altro. Consulta il wiki di Rack GitHub per un elenco di middleware .
Middleware è un termine terribile che si riferisce a qualsiasi componente / libreria software che assiste ma non è direttamente coinvolto nell'esecuzione di alcune attività. Esempi molto comuni sono la registrazione, l'autenticazione e gli altri componenti comuni di elaborazione orizzontale . Queste tendono ad essere le cose di cui tutti hanno bisogno in più applicazioni ma non troppe persone sono interessate (o dovrebbero essere) a costruirsi.
Il commento sul fatto che sia un modo per filtrare le richieste proviene probabilmente dall'episodio 151 di RailsCast: cast dello schermo di Middleware in rack .
Il middleware Rack si è evoluto da Rack e c'è una grande introduzione in Introduzione al middleware Rack .
C'è un'introduzione al middleware su Wikipedia qui .
Prima di tutto, Rack è esattamente due cose:
Rack: l'interfaccia Webserver
Le basi del rack sono una convenzione semplice. Ogni server Web conforme al rack chiamerà sempre un metodo di chiamata su un oggetto che gli viene fornito e servirà il risultato di tale metodo. Rack specifica esattamente come deve apparire questo metodo di chiamata e cosa deve restituire. Questo è un rack.
Facciamo un semplice tentativo. Userò WEBrick come server Web compatibile con rack, ma uno qualsiasi di essi lo farà. Creiamo una semplice applicazione Web che restituisce una stringa JSON. Per questo creeremo un file chiamato config.ru. Config.ru verrà chiamato automaticamente dal comando di rackup della gemma che eseguirà semplicemente il contenuto di config.ru in un server Web compatibile con il rack. Quindi aggiungiamo quanto segue al file config.ru:
class JSONServer
def call(env)
[200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
end
end
map '/hello.json' do
run JSONServer.new
end
Come specificato dalla convenzione, il nostro server ha un metodo chiamato call che accetta un hash di ambiente e restituisce un array con il formato [stato, intestazioni, corpo] per il server web da servire. Proviamo semplicemente chiamando il rackup. Un server conforme a rack predefinito, forse WEBrick o Mongrel, si avvierà e attenderà immediatamente che le richieste vengano pubblicate.
$ rackup
[2012-02-19 22:39:26] INFO WEBrick 1.3.1
[2012-02-19 22:39:26] INFO ruby 1.9.3 (2012-01-17) [x86_64-darwin11.2.0]
[2012-02-19 22:39:26] INFO WEBrick::HTTPServer#start: pid=16121 port=9292
Testiamo il nostro nuovo server JSON curling o visitando l'URL http://localhost:9292/hello.json
e voilà:
$ curl http://localhost:9292/hello.json
{ message: "Hello!" }
Funziona. Grande! Questa è la base per ogni framework web, sia esso Rails o Sinatra. Ad un certo punto implementano un metodo call, lavorano attraverso tutto il codice del framework e infine restituiscono una risposta nella tipica forma [stato, intestazioni, corpo].
In Ruby on Rails, ad esempio, le richieste del rack raggiungono la ActionDispatch::Routing.Mapper
classe che assomiglia a questa:
module ActionDispatch
module Routing
class Mapper
...
def initialize(app, constraints, request)
@app, @constraints, @request = app, constraints, request
end
def matches?(env)
req = @request.new(env)
...
return true
end
def call(env)
matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ]
end
...
end
end
Quindi, in pratica, i controlli di Rails dipendono dall'hash di env se una route corrisponde. In tal caso, passa l'hash env all'applicazione per calcolare la risposta, altrimenti risponde immediatamente con un 404. Quindi qualsiasi server Web conforme alla convenzione sull'interfaccia del rack è in grado di servire un'applicazione Rails completamente funzionante.
middleware
Rack supporta anche la creazione di livelli middleware. Fondamentalmente intercettano una richiesta, fanno qualcosa con essa e la trasmettono. Questo è molto utile per compiti versatili.
Supponiamo di voler aggiungere la registrazione al nostro server JSON che misura anche il tempo impiegato da una richiesta. Possiamo semplicemente creare un logger middleware che fa esattamente questo:
class RackLogger
def initialize(app)
@app = app
end
def call(env)
@start = Time.now
@status, @headers, @body = @app.call(env)
@duration = ((Time.now - @start).to_f * 1000).round(2)
puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms"
[@status, @headers, @body]
end
end
Quando viene creato, si salva una copia dell'applicazione rack effettiva. Nel nostro caso questa è un'istanza del nostro JSONServer. Rack chiama automaticamente il metodo di chiamata sul middleware e si aspetta un [status, headers, body]
array, proprio come il nostro server JSON restituisce.
Quindi, in questo middleware, viene preso il punto iniziale, quindi viene effettuata la chiamata effettiva a JSONServer @app.call(env)
, quindi il logger emette la voce di registrazione e infine restituisce la risposta come [@status, @headers, @body]
.
Per fare in modo che il nostro piccolo rackup.ru utilizzi questo middleware, aggiungi un RackLogger ad esso in questo modo:
class JSONServer
def call(env)
[200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
end
end
class RackLogger
def initialize(app)
@app = app
end
def call(env)
@start = Time.now
@status, @headers, @body = @app.call(env)
@duration = ((Time.now - @start).to_f * 1000).round(2)
puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms"
[@status, @headers, @body]
end
end
use RackLogger
map '/hello.json' do
run JSONServer.new
end
Riavvia il server e voilà, genera un registro su ogni richiesta. Rack consente di aggiungere più middleware chiamati nell'ordine in cui sono stati aggiunti. È solo un ottimo modo per aggiungere funzionalità senza cambiare il core dell'applicazione rack.
Rack - The Gem
Sebbene il rack - prima di tutto - sia una convenzione, è anche un gioiello che offre grandi funzionalità. Uno di quelli che abbiamo già utilizzato per il nostro server JSON, il comando di rackup. Ma c'è di più! La gemma del rack offre piccole applicazioni per molti casi d'uso, come servire file statici o persino intere directory. Vediamo come serviamo un semplice file, ad esempio un file HTML di base che si trova in htmls / index.html:
<!DOCTYPE HTML>
<html>
<head>
<title>The Index</title>
</head>
<body>
<p>Index Page</p>
</body>
</html>
Forse vogliamo servire questo file dalla radice del sito web, quindi aggiungiamo quanto segue al nostro config.ru:
map '/' do
run Rack::File.new "htmls/index.html"
end
Se visitiamo http://localhost:9292
vediamo il nostro file html reso perfettamente. È stato facile, vero?
Aggiungiamo un'intera directory di file javascript creando alcuni file javascript in / javascripts e aggiungendo quanto segue a config.ru:
map '/javascripts' do
run Rack::Directory.new "javascripts"
end
Riavvia il server e visita http://localhost:9292/javascript
e vedrai un elenco di tutti i file javascript che puoi includere ora direttamente da qualsiasi luogo.
Ho avuto un problema a capire Rack da solo per un bel po 'di tempo. L'ho capito appieno solo dopo aver lavorato sul creare questo web server Ruby in miniatura da solo. Ho condiviso le mie conoscenze su Rack (sotto forma di una storia) qui sul mio blog: http://gauravchande.com/what-is-rack-in-ruby-rails
Il feedback è più che benvenuto.
config.ru
esempio eseguibile minimo
app = Proc.new do |env|
[
200,
{
'Content-Type' => 'text/plain'
},
["main\n"]
]
end
class Middleware
def initialize(app)
@app = app
end
def call(env)
@status, @headers, @body = @app.call(env)
[@status, @headers, @body << "Middleware\n"]
end
end
use(Middleware)
run(app)
Corri rackup
e visita localhost:9292
. L'output è:
main
Middleware
Quindi è chiaro che Middleware
avvolge e chiama l'app principale. Pertanto è in grado di pre-elaborare la richiesta e post-elaborazione della risposta in qualsiasi modo.
Come spiegato su: http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack , Rails utilizza i middleware Rack per molte delle sue funzionalità e puoi aggiungerne anche tu con config.middleware.use
i metodi familiari.
Il vantaggio dell'implementazione della funzionalità in un middleware è che è possibile riutilizzarlo su qualsiasi framework Rack, quindi su tutti i principali Ruby e non solo su Rails.
Il middleware rack è un modo per filtrare una richiesta e una risposta che arrivano nell'applicazione. Un componente middleware si trova tra il client e il server, elaborando le richieste in entrata e le risposte in uscita, ma è più che un'interfaccia che può essere utilizzata per parlare con il web server. È usato per raggruppare e ordinare moduli, che di solito sono classi Ruby, e specificare la dipendenza tra di loro. Il modulo middleware per rack deve solo: - avere un costruttore che prende la prossima applicazione nello stack come parametro - rispondere al metodo "call", che utilizza l'hash dell'ambiente come parametro. Il valore restituito da questa chiamata è un array di: codice di stato, hash dell'ambiente e corpo della risposta.
Ho usato il middleware Rack per risolvere un paio di problemi:
Ha offerto correzioni piuttosto eleganti in entrambi i casi.
Rack fornisce un'interfaccia minima tra i server Web che supportano i framework Ruby e Ruby.
Utilizzando Rack è possibile scrivere un'applicazione Rack.
Rack passerà l'hash Environment (un hash, contenuto all'interno di una richiesta HTTP da un client, costituito da intestazioni simili a CGI) all'applicazione Rack che può usare le cose contenute in questo hash per fare quello che vuole.
Per utilizzare Rack, è necessario fornire un'app, un oggetto che risponde al #call
metodo con l'hash dell'ambiente come parametro (generalmente definito come env
). #call
deve restituire una matrice con esattamente tre valori:
each
).Puoi scrivere un'applicazione Rack che restituisce un tale array - questo verrà rispedito al tuo client, da Rack, all'interno di una risposta (questa sarà in realtà un'istanza della classe Rack::Response
[fai clic per andare ai documenti]).
gem install rack
config.ru
file: Rack sa cercarlo.Creeremo una piccola applicazione rack che restituisce una risposta (un'istanza Rack::Response
) che è corpo di risposta è un array che contiene una stringa: "Hello, World!"
.
Accenderemo un server locale usando il comando rackup
.
Quando visiteremo la porta pertinente nel nostro browser vedremo "Ciao, mondo!" reso nel viewport.
#./message_app.rb
class MessageApp
def call(env)
[200, {}, ['Hello, World!']]
end
end
#./config.ru
require_relative './message_app'
run MessageApp.new
Avvia un server locale con rackup
e visita localhost: 9292 e dovresti vedere 'Hello, World!' reso.
Questa non è una spiegazione esaustiva, ma essenzialmente ciò che accade qui è che il Client (il browser) invia una richiesta HTTP a Rack, tramite il server locale, e Rack istanzia MessageApp
ed esegue call
, passando l'Hash Ambiente come parametro nel metodo ( l' env
argomento).
Il rack accetta il valore restituito (l'array) e lo utilizza per creare un'istanza di Rack::Response
e inviarlo al client. Il browser utilizza la magia per stampare "Hello, World!" allo schermo.
Per inciso, se vuoi vedere com'è l'hash dell'ambiente, mettici puts env
sotto def call(env)
.
Per quanto minimale, quello che hai scritto qui è un'applicazione Rack!
Nella nostra piccola app Rack, possiamo interagire con l' env
hash (vedi qui per ulteriori informazioni sull'hash dell'ambiente).
Implementeremo la possibilità per l'utente di inserire la propria stringa di query nell'URL, pertanto tale stringa sarà presente nella richiesta HTTP, incapsulata come valore in una delle coppie chiave / valore dell'hash Environment.
La nostra app Rack accederà a quella stringa di query dall'hash Environment e la invierà al client (il nostro browser, in questo caso) tramite il Body in the Response.
Dai documenti Rack sull'hash dell'ambiente: "QUERY_STRING: la parte dell'URL della richiesta che segue il?, Se presente. Può essere vuota, ma è sempre richiesta!"
#./message_app.rb
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
Ora, rackup
visita localhost:9292?hello
( ?hello
essendo la stringa della query) e dovresti vedere 'ciao' reso nel viewport.
Noi:
MessageSetter
,env
,MessageSetter
inserirà una 'MESSAGE'
chiave nell'hash di env, il cui valore è 'Hello, World!'
se env['QUERY_STRING']
è vuoto; env['QUERY_STRING']
altrimenti,@app.call(env)
- @app
di essere la prossima app nel 'stack': MessageApp
.Innanzitutto, la versione "long-hand":
#./middleware/message_setter.rb
class MessageSetter
def initialize(app)
@app = app
end
def call(env)
if env['QUERY_STRING'].empty?
env['MESSAGE'] = 'Hello, World!'
else
env['MESSAGE'] = env['QUERY_STRING']
end
@app.call(env)
end
end
#./message_app.rb (same as before)
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
#config.ru
require_relative './message_app'
require_relative './middleware/message_setter'
app = Rack::Builder.new do
use MessageSetter
run MessageApp.new
end
run app
Dai documenti Rack :: Builder vediamo che Rack::Builder
implementa un piccolo DSL per costruire iterativamente applicazioni Rack. Ciò significa sostanzialmente che è possibile creare uno "Stack" composto da uno o più Middleware e un'applicazione di "livello inferiore" a cui inviare. Tutte le richieste che passano all'applicazione di livello inferiore verranno prima elaborate dal tuo Middleware.
#use
specifica il middleware da utilizzare in una pila. Prende il middleware come argomento.
Il Middleware Rack deve:
call
metodo che utilizza l'hash Environment come parametro.Nel nostro caso, il "Middleware" è MessageSetter
, il "costruttore" è il initialize
metodo di MessageSetter , la "prossima applicazione" nello stack è MessageApp
.
Così qui, a causa di ciò che Rack::Builder
ha sotto il cofano, l' app
argomento del MessageSetter
's initialize
metodo è MessageApp
.
(aggira la testa prima di andare avanti)
Pertanto, ogni pezzo di Middleware essenzialmente "passa" l'hash Ambiente esistente alla successiva applicazione nella catena - quindi hai l'opportunità di mutare l'hash ambiente all'interno del Middleware prima di passarlo all'applicazione successiva nello stack.
#run
accetta un argomento che è un oggetto che risponde #call
e restituisce una risposta in rack (un'istanza di Rack::Response
).
Usando Rack::Builder
puoi costruire catene di Middleware e qualsiasi richiesta alla tua applicazione verrà elaborata da ciascun Middleware a sua volta prima di essere infine elaborata dal pezzo finale nello stack (nel nostro caso MessageApp
). Ciò è estremamente utile perché separa le diverse fasi di elaborazione delle richieste. In termini di "separazione delle preoccupazioni", non potrebbe essere molto più pulito!
Puoi costruire una 'pipeline di richieste' composta da diversi Middleware che trattano cose come:
(sopra i punti elenco di un'altra risposta su questa discussione)
Lo vedrai spesso nelle applicazioni Sinatra professionali. Sinatra usa Rack! Vedi qui per la definizione di ciò che Sinatra È !
Come nota finale, il nostro config.ru
può essere scritto in uno stile di breve durata, producendo esattamente la stessa funzionalità (e questo è ciò che vedrai in genere):
require_relative './message_app'
require_relative './middleware/message_setter'
use MessageSetter
run MessageApp.new
E per mostrare più esplicitamente ciò che MessageApp
sta facendo, ecco la sua versione "long-hand" che mostra esplicitamente che #call
sta creando una nuova istanza di Rack::Response
, con i tre argomenti richiesti.
class MessageApp
def call(env)
Rack::Response.new([env['MESSAGE']], 200, {})
end
end
Rack: l'interfaccia b / n server Web e app
Rack è un pacchetto Ruby che fornisce un'interfaccia per un server Web per comunicare con l'applicazione. È facile aggiungere componenti middleware tra il server Web e l'app per modificare il comportamento della richiesta / risposta. Il componente middleware si trova tra il client e il server, elaborando le richieste in entrata e le risposte in uscita.
In parole povere, è fondamentalmente solo una serie di linee guida su come un server e un'app Rails (o qualsiasi altra app Web Ruby) dovrebbero dialogare .
Per utilizzare Rack, fornire una "app": un oggetto che risponde al metodo di chiamata, prendendo l'hash dell'ambiente come parametro e restituendo una matrice con tre elementi:
Per ulteriori spiegazioni, è possibile seguire i collegamenti seguenti.
1. https://rack.github.io/
2. https://redpanthers.co/rack-middleware/
3. https://blog.engineyard.com/2015/understanding-rack-apps-and-middleware
4. https://guides.rubyonrails.org/rails_on_rack.html#resources
In rails, abbiamo config.ru come file rack, puoi eseguire qualsiasi file rack con rackup
comando. E la porta predefinita per questo è 9292
. Per verificarlo, puoi semplicemente eseguirlo rackup
nella directory delle rotaie e vedere il risultato. È inoltre possibile assegnare la porta su cui si desidera eseguirlo. Il comando per eseguire il file rack su qualsiasi porta specifica è
rackup -p PORT_NUMBER
Rack è un gioiello che fornisce una semplice interfaccia per astrarre la richiesta / risposta HTTP. Il rack si trova tra i framework Web (Rails, Sinatra ecc.) E i server Web (unicorno, puma) come un adattatore. Dall'immagine sopra ciò mantiene il server unicorno completamente indipendente dalla conoscenza delle rotaie e le rotaie non conoscono l'unicorno. Questo è un buon esempio di accoppiamento lento , separazione delle preoccupazioni .
L'immagine qui sopra proviene da questa conferenza conferenza su rotaie su rack https://youtu.be/3PnUV9QzB0g Consiglio di guardarla per una comprensione più profonda.