Qu'est-ce que le middleware Rack dans Ruby? Je n'ai trouvé aucune bonne explication de ce qu'ils entendent par "middleware".
Qu'est-ce que le middleware Rack dans Ruby? Je n'ai trouvé aucune bonne explication de ce qu'ils entendent par "middleware".
Réponses:
L'intergiciel rack est plus qu'une "façon de filtrer une demande et une réponse" - c'est une implémentation du modèle de conception de pipeline pour les serveurs Web utilisant Rack .
Il sépare très clairement les différentes étapes du traitement d'une demande - la séparation des préoccupations est un objectif clé de tous les produits logiciels bien conçus.
Par exemple, avec Rack, je peux avoir des étapes distinctes du pipeline:
Authentification : lorsque la demande arrive, les informations de connexion des utilisateurs sont-elles correctes? Comment puis-je valider cet OAuth, authentification HTTP de base, nom / mot de passe?
Autorisation : "l'utilisateur est-il autorisé à effectuer cette tâche particulière?", C'est-à-dire la sécurité basée sur les rôles.
Mise en cache : ai-je déjà traité cette demande, puis-je retourner un résultat mis en cache?
Décoration : comment améliorer la demande pour améliorer le traitement en aval?
Surveillance des performances et de l'utilisation : quelles statistiques puis-je obtenir de la demande et de la réponse?
Exécution : gérer réellement la demande et fournir une réponse.
Être capable de séparer les différentes étapes (et éventuellement les inclure) est d'une grande aide pour développer des applications bien structurées.
Il existe également un excellent écosystème autour du Rack Middleware - vous devriez pouvoir trouver des composants de rack prédéfinis pour effectuer toutes les étapes ci-dessus et plus encore. Voir le wiki Rack GitHub pour une liste de middleware .
Le middleware est un terme épouvantable qui fait référence à tout composant logiciel / bibliothèque qui aide mais n'est pas directement impliqué dans l'exécution de certaines tâches. Des exemples très courants sont la journalisation, l'authentification et les autres composants de traitement horizontaux courants . Ce sont généralement les choses dont tout le monde a besoin dans plusieurs applications, mais pas trop de gens sont intéressés (ou devraient être) à se construire.
Le commentaire selon lequel il s'agit d'un moyen de filtrer les demandes vient probablement de l' épisode 151 de RailsCast: Cast Screen Screen Middleware .
Le middleware de rack a évolué à partir de Rack et il y a une excellente introduction à Introduction au middleware de rack .
Il y a une introduction au middleware sur Wikipedia ici .
Tout d'abord, Rack, c'est exactement deux choses:
Rack - L'interface du serveur Web
Les bases mêmes du rack sont une convention simple. Chaque serveur Web conforme au rack appellera toujours une méthode d'appel sur un objet que vous lui donnez et servira le résultat de cette méthode. Rack spécifie exactement à quoi doit ressembler cette méthode d'appel et ce qu'elle doit renvoyer. C'est du rack.
Essayons simplement. J'utiliserai WEBrick comme serveur Web compatible avec les racks, mais n'importe lequel d'entre eux fera l'affaire. Créons une application Web simple qui renvoie une chaîne JSON. Pour cela, nous allons créer un fichier appelé config.ru. Le config.ru sera automatiquement appelé par la commande rackup du rack gem qui exécutera simplement le contenu du config.ru dans un serveur Web compatible avec le rack. Ajoutons donc ce qui suit au fichier config.ru:
class JSONServer
def call(env)
[200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
end
end
map '/hello.json' do
run JSONServer.new
end
Comme la convention le précise, notre serveur a une méthode appelée call qui accepte un hachage d'environnement et retourne un tableau avec la forme [status, headers, body] pour le serveur web à servir. Essayons-le en appelant simplement rackup. Un serveur par défaut compatible rack, peut-être que WEBrick ou Mongrel démarrera et attendra immédiatement que les demandes soient traitées.
$ 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
Testons notre nouveau serveur JSON en bouclant ou en visitant l'url http://localhost:9292/hello.json
et le tour est joué:
$ curl http://localhost:9292/hello.json
{ message: "Hello!" }
Ça marche. Génial! C'est la base de chaque framework web, que ce soit Rails ou Sinatra. À un moment donné, ils implémentent une méthode d'appel, parcourent tout le code du framework et retournent finalement une réponse sous la forme typique [état, en-têtes, corps].
Dans Ruby on Rails par exemple, les requêtes de rack atteignent la ActionDispatch::Routing.Mapper
classe qui ressemble à ceci:
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
Donc, fondamentalement, Rails vérifie, en fonction du hachage env si une route correspond. Si c'est le cas, il transmet le hachage env à l'application pour calculer la réponse, sinon il répond immédiatement avec un 404. Ainsi, tout serveur Web qui est conforme à la convention d'interface de rack est capable de servir une application Rails complètement soufflée.
Middleware
Rack prend également en charge la création de couches middleware. Ils interceptent essentiellement une demande, en font quelque chose et la transmettent. Ceci est très utile pour des tâches polyvalentes.
Supposons que nous souhaitons ajouter une journalisation à notre serveur JSON qui mesure également la durée d'une demande. Nous pouvons simplement créer un enregistreur de middleware qui fait exactement cela:
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
Lorsqu'il est créé, il enregistre lui-même une copie de l'application rack réelle. Dans notre cas, c'est une instance de notre JSONServer. Rack appelle automatiquement la méthode d'appel sur le middleware et attend un [status, headers, body]
tableau, tout comme le retourne notre JSONServer.
Ainsi, dans ce middleware, le point de départ est pris, puis l'appel réel au JSONServer est effectué avec @app.call(env)
, puis l'enregistreur sort l'entrée de journalisation et renvoie finalement la réponse sous la forme [@status, @headers, @body]
.
Pour que notre petit rackup.ru utilise ce middleware, ajoutez-lui un RackLogger comme ceci:
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
Redémarrez le serveur et le tour est joué, il génère un journal à chaque demande. Rack vous permet d'ajouter plusieurs middlewares qui sont appelés dans l'ordre où ils sont ajoutés. C'est juste un excellent moyen d'ajouter des fonctionnalités sans changer le cœur de l'application rack.
Rack - The Gem
Bien que le rack - tout d'abord - soit une convention, c'est aussi un joyau qui offre une grande fonctionnalité. Nous en avons déjà utilisé un pour notre serveur JSON, la commande rackup. Mais il y a plus! Le joyau du rack fournit de petites applications pour de nombreux cas d'utilisation, comme le service de fichiers statiques ou même des répertoires entiers. Voyons comment nous servons un fichier simple, par exemple un fichier HTML très basique situé à htmls / index.html:
<!DOCTYPE HTML>
<html>
<head>
<title>The Index</title>
</head>
<body>
<p>Index Page</p>
</body>
</html>
Nous voulons peut-être servir ce fichier à partir de la racine du site Web, alors ajoutons ce qui suit à notre config.ru:
map '/' do
run Rack::File.new "htmls/index.html"
end
Si nous visitons, http://localhost:9292
nous voyons notre fichier html parfaitement rendu. C'était facile, non?
Ajoutons tout un répertoire de fichiers javascript en créant des fichiers javascript sous / javascripts et en ajoutant ce qui suit au config.ru:
map '/javascripts' do
run Rack::Directory.new "javascripts"
end
Redémarrez le serveur et visitez http://localhost:9292/javascript
et vous verrez une liste de tous les fichiers javascript que vous pouvez inclure maintenant directement de n'importe où.
J'ai eu du mal à comprendre Rack moi-même pendant un bon bout de temps. Je ne l'ai bien compris qu'après avoir travaillé moi-même à la création de ce serveur Web miniature Ruby . J'ai partagé mes connaissances sur Rack (sous la forme d'une histoire) ici sur mon blog: http://gauravchande.com/what-is-rack-in-ruby-rails
Les commentaires sont plus que bienvenus.
config.ru
exemple exécutable minimal
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)
Courez rackup
et visitez localhost:9292
. La sortie est:
main
Middleware
Il est donc clair que le Middleware
wraps et appelle l'application principale. Par conséquent, il est capable de prétraiter la demande et de post-traiter la réponse de n'importe quelle manière.
Comme expliqué sur: http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack , Rails utilise les middlewares de Rack pour une grande partie de ses fonctionnalités, et vous pouvez aussi en ajouter vous-même avec config.middleware.use
des méthodes familiales.
L'avantage d'implémenter des fonctionnalités dans un middleware est que vous pouvez les réutiliser sur n'importe quel framework Rack, donc tous les Ruby majeurs, et pas seulement Rails.
Le middleware de rack est un moyen de filtrer une demande et une réponse entrant dans votre application. Un composant middleware se situe entre le client et le serveur, traitant les demandes entrantes et les réponses sortantes, mais c'est plus qu'une interface qui peut être utilisée pour parler au serveur Web. Il est utilisé pour regrouper et ordonner les modules, qui sont généralement des classes Ruby, et spécifier la dépendance entre eux. Le module middleware de rack doit uniquement: - avoir un constructeur qui prend la prochaine application dans la pile comme paramètre - répondre à la méthode «call», qui prend le hachage de l'environnement comme paramètre. La valeur de retour de cet appel est un tableau de: code d'état, hachage d'environnement et corps de réponse.
J'ai utilisé le middleware Rack pour résoudre quelques problèmes:
Il offrait des correctifs assez élégants dans les deux cas.
Rack fournit une interface minimale entre les serveurs Web prenant en charge les frameworks Ruby et Ruby.
En utilisant Rack, vous pouvez écrire une application Rack.
Rack transmettra le hachage d'environnement (un hachage, contenu dans une demande HTTP d'un client, composé d'en-têtes de type CGI) à votre application de rack qui peut utiliser les éléments contenus dans ce hachage pour faire ce qu'il veut.
Pour utiliser Rack, vous devez fournir une «application» - un objet qui répond à la #call
méthode avec le hachage d'environnement comme paramètre (généralement défini comme env
). #call
doit renvoyer un tableau d'exactement trois valeurs:
each
).Vous pouvez écrire une application Rack qui retourne un tel tableau - cela sera renvoyé à votre client, par Rack, dans une réponse (ce sera en fait une instance de la classeRack::Response
[cliquez pour accéder aux documents]).
gem install rack
config.ru
fichier - Rack sait le chercher.Nous allons créer une petite application en rack qui renvoie une réponse (une instance de Rack::Response
) qui est le corps de réponse est un tableau qui contient une chaîne:"Hello, World!"
.
Nous allons lancer un serveur local en utilisant la commande rackup
.
Lorsque vous visitez le port concerné dans notre navigateur, nous verrons "Bonjour, monde!" rendu dans la fenêtre.
#./message_app.rb
class MessageApp
def call(env)
[200, {}, ['Hello, World!']]
end
end
#./config.ru
require_relative './message_app'
run MessageApp.new
Démarrez un serveur local avec rackup
et visitez localhost: 9292 et vous devriez voir «Bonjour, World! rendu.
Ce n'est pas une explication complète, mais essentiellement ce qui se passe ici est que le client (le navigateur) envoie une demande HTTP à Rack, via votre serveur local, et Rack instancie MessageApp
et s'exécute call
, en passant le hachage d'environnement en tant que paramètre dans la méthode ( lesenv
argument).
Rack prend la valeur de retour (le tableau) et l'utilise pour créer une instance de Rack::Response
et la renvoie au client. Le navigateur utilise la magie pour imprimer «Bonjour tout le monde! à l'écran.
Soit dit en passant, si vous voulez voir à quoi ressemble le hachage de l'environnement, placez-le juste en puts env
dessousdef call(env)
.
Aussi minime soit-elle, ce que vous avez écrit ici est une application Rack!
Dans notre petite application Rack, nous pouvons interagir avec le env
hachage (voir ici pour en savoir plus sur le hachage Environnement).
Nous mettrons en œuvre la possibilité pour l'utilisateur d'entrer sa propre chaîne de requête dans l'URL, par conséquent, cette chaîne sera présente dans la demande HTTP, encapsulée en tant que valeur dans l'une des paires clé / valeur du hachage d'environnement.
Notre application Rack accédera à cette chaîne de requête à partir du hachage d'environnement et la renverra au client (notre navigateur, dans ce cas) via le corps de la réponse.
À partir des documents Rack sur le hachage d'environnement: "QUERY_STRING: la partie de l'URL de demande qui suit le?, Le cas échéant. Peut être vide, mais est toujours obligatoire!"
#./message_app.rb
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
Maintenant, rackup
et visitez localhost:9292?hello
( ?hello
étant la chaîne de requête) et vous devriez voir «bonjour» rendu dans la fenêtre.
Nous allons:
MessageSetter
,env
,MessageSetter
insérera une 'MESSAGE'
clé dans le hachage env, sa valeur étant 'Hello, World!'
si env['QUERY_STRING']
est vide;env['QUERY_STRING']
si non,@app.call(env)
- @app
être la prochaine application de la « pile »: MessageApp
.Tout d'abord, la version «longue»:
#./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
À partir des documents Rack :: Builder, nous voyons queRack::Builder
implémente un petit DSL pour construire de manière itérative des applications Rack. Cela signifie essentiellement que vous pouvez créer une «pile» composée d'un ou plusieurs middlewares et d'une application de «bas niveau» vers laquelle envoyer. Toutes les demandes transmises à votre application de niveau inférieur seront d'abord traitées par votre ou vos middleware.
#use
spécifie le middleware à utiliser dans une pile. Il prend le middleware comme argument.
Le middleware de rack doit:
call
méthode qui prend le hachage d'environnement comme paramètre.Dans notre cas, le «middleware» est MessageSetter
, le «constructeur» est la initialize
méthode de MessageSetter , la «prochaine application» dans la pile estMessageApp
.
Donc ici, à cause de ce qui se Rack::Builder
passe sous le capot, l' app
argument de MessageSetter
la initialize
méthode est MessageApp
.
(faites le tour de la tête avant de continuer)
Par conséquent, chaque élément du middleware `` transmet '' essentiellement le hachage d'environnement existant à l'application suivante de la chaîne - vous avez donc la possibilité de muter ce hachage d'environnement au sein du middleware avant de le transmettre à l'application suivante de la pile.
#run
prend un argument qui est un objet qui répond #call
et renvoie une réponse de rack (une instance de Rack::Response
).
En utilisant Rack::Builder
vous pouvez construire des chaînes de middlewares et toute demande à votre application sera traitée à tour de rôle par chaque middleware avant d'être finalement traitée par la dernière pièce de la pile (dans notre cas,MessageApp
). Ceci est extrêmement utile car il sépare les différentes étapes du traitement des demandes. En termes de «séparation des préoccupations», cela ne pourrait pas être beaucoup plus propre!
Vous pouvez construire un «pipeline de demandes» composé de plusieurs middlewares qui traitent de choses telles que:
(au-dessus des puces d'une autre réponse sur ce fil)
Vous le verrez souvent dans les applications professionnelles Sinatra. Sinatra utilise Rack! Voir ici pour la définition de ce que Sinatra EST !
En guise de note finale, notre config.ru
peut être écrit dans un style abrégé, produisant exactement la même fonctionnalité (et c'est ce que vous verrez généralement):
require_relative './message_app'
require_relative './middleware/message_setter'
use MessageSetter
run MessageApp.new
Et pour montrer plus explicitement ce qui MessageApp
se passe, voici sa version «longue» qui montre explicitement que #call
crée une nouvelle instance de Rack::Response
, avec les trois arguments requis.
class MessageApp
def call(env)
Rack::Response.new([env['MESSAGE']], 200, {})
end
end
Rack - L'interface b / w Web & App Server
Rack est un package Ruby qui fournit une interface permettant à un serveur Web de communiquer avec l'application. Il est facile d'ajouter des composants middleware entre le serveur Web et l'application pour modifier le comportement de votre demande / réponse. Le composant middleware se situe entre le client et le serveur, traitant les demandes entrantes et les réponses sortantes.
En termes simples, il s'agit essentiellement d'un ensemble de directives sur la façon dont un serveur et une application Rails (ou toute autre application Web Ruby) doivent se parler .
Pour utiliser Rack, fournissez une "application": un objet qui répond à la méthode d'appel, en prenant le hachage de l'environnement comme paramètre et en renvoyant un tableau avec trois éléments:
Pour plus d'explications, vous pouvez suivre les liens ci-dessous.
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
Dans les rails, nous avons config.ru comme fichier rack, vous pouvez exécuter n'importe quel fichier rack avec la rackup
commande. Et le port par défaut pour cela est 9292
. Pour tester cela, vous pouvez simplement exécuter rackup
dans votre répertoire rails et voir le résultat. Vous pouvez également attribuer le port sur lequel vous souhaitez l'exécuter. La commande pour exécuter le fichier rack sur n'importe quel port spécifique est
rackup -p PORT_NUMBER
Le rack est un joyau qui fournit une interface simple pour abstraire les requêtes / réponses HTTP. Le rack se situe entre les cadres Web (Rails, Sinatra, etc.) et les serveurs Web (licorne, puma) en tant qu'adaptateur. De l'image ci-dessus, cela permet au serveur de licorne d'être complètement indépendant de connaître les rails et les rails ne connaissent pas la licorne. Ceci est un bon exemple de couplage lâche , de séparation des préoccupations .
L'image ci-dessus provient de cette conférence sur les rails sur le rack https://youtu.be/3PnUV9QzB0g Je recommande de le regarder pour une compréhension plus approfondie.