Qu'est-ce que le middleware Rack en Ruby? Je ne pouvais pas trouver une bonne explication à ce qu'ils entendent par "middleware".
Le middleware Rack est plus qu'un "moyen de filtrer une requête et une réponse" - il s'agit d'une implémentation de modèle de conception de pipeline pour les serveurs Web utilisant Rack .
Il sépare très proprement 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 différentes étapes du pipeline:
Authentification : lorsque la demande arrive, les informations de connexion des utilisateurs sont-elles correctes? Comment valider cette authentification OAuth, HTTP Basic, 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 puis-je 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ère la demande et fournit une réponse.
Pouvoir séparer les différentes étapes (et éventuellement les inclure) est une aide précieuse pour développer des applications bien structurées.
Il existe également un excellent écosystème en développement autour de Rack Middleware - vous devriez pouvoir trouver des composants de bâti prédéfinis pour effectuer toutes les étapes ci-dessus et bien plus encore. Voir le wiki de Rack GitHub pour une liste de middleware .
Middleware est un terme redoutable qui fait référence à tout composant logiciel/bibliothèque qui assiste mais ne participe pas directement à l'exécution de certaines tâches. Des exemples très courants sont la journalisation, l’authentification et les autres composants de traitement horizontaux communs . Celles-ci ont tendance à être celles dont tout le monde a besoin dans plusieurs applications, mais peu de personnes sont intéressées (ou devraient l'être) à se construire elles-mêmes.
Le commentaire sur le fait qu'il s'agisse d'un moyen de filtrer les demandes provient probablement de la diffusion d'écran RailsCast épisode 151: Rack Middleware .
Le middleware Rack a évolué à partir de Rack et il existe une excellente introduction à Introduction au middleware Rack .
Il y a une introduction au middleware sur Wikipedia ici .
Tout d’abord, Rack, c’est exactement deux choses:
Rack - L'interface serveur Web
La base même de rack est une convention simple. Chaque serveur Web compatible avec le rack appellera toujours une méthode d'appel sur un objet que vous lui aurez donné et transmettra le résultat de cette méthode. Rack spécifie exactement à quoi cette méthode d’appel doit ressembler et ce qu’elle doit renvoyer. C'est rack.
Essayons-le simplement. J'utiliserai WEBrick comme serveur Web compatible avec le rack, mais aucun d'entre eux ne conviendra. Créons une application Web simple qui renvoie une chaîne JSON. Pour cela, nous allons créer un fichier appelé config.ru. Le fichier config.ru sera automatiquement appelé par la commande rackup du rack gem, qui exécutera simplement le contenu du fichier 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 le stipule la convention, notre serveur a une méthode appelée call qui accepte un hachage d’environnement et renvoie un tableau sous la forme [statut, en-têtes, corps] que le serveur Web doit servir. Essayons-le simplement en appelant rackup. Un serveur compatible avec le rack par défaut, peut-être WEBrick ou Mongrel, démarrera-t-il et attendra-t-il immédiatement les demandes à servir.
$ 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 soit en curling, soit en visitant l'URL http://localhost:9292/hello.json
Et le tour suivant:
$ curl http://localhost:9292/hello.json
{ message: "Hello!" }
Ça marche. Génial! C’est la base de tout framework web, que ce soit Rails ou Sinatra. À un moment donné, ils implémentent une méthode d’appel, utilisent tout le code du framework et finalement retournent une réponse dans le statut [status, en-têtes, corps] forme.
Dans Ruby sur Rails, par exemple, les demandes de rack touchent la classe ActionDispatch::Routing.Mapper
, 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 hash env si une route correspond. Si tel est le cas, il transmet le hash env à l’application pour calculer la réponse, sinon il répond immédiatement avec un 404. Le serveur Web qui est conforme à la convention d'interface de rack est capable de servir une application Rails totalement soufflée).
Middleware
Rack prend également en charge la création de couches middleware. Ils interceptent une requête, en font quelque chose et la transmettent. Ceci est très utile pour les tâches polyvalentes.
Supposons que nous voulions ajouter une journalisation à notre serveur JSON, qui mesure également la durée d'une demande. Nous pouvons simplement créer un logger middleware qui fait exactement ceci:
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, il s’agit d’une instance de notre serveur JSONS. Rack appelle automatiquement la méthode d'appel sur le middleware et attend un tableau [status, headers, body]
, Comme le retourne notre serveur JSONServer.
Ainsi, dans ce middleware, le point de départ est pris, puis l'appel réel à JSONServer est effectué avec @app.call(env)
, puis le consignateur génère l'entrée de consignation et renvoie finalement la réponse sous la forme [@status, @headers, @body]
.
Pour que notre petit rackup.ru utilise ce middleware, ajoutez un RackLogger à utiliser 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é, un journal est généré pour chaque demande. Rack vous permet d'ajouter plusieurs middlewares appelés dans l'ordre dans lequel ils ont été ajoutés. C'est simplement un excellent moyen d'ajouter des fonctionnalités sans changer le cœur de l'application en rack.
Rack - The Gem
Bien que le rack - tout d’abord - soit une convention, c’est aussi un joyau qui offre une grande fonctionnalité. L'un d'eux, nous avons déjà utilisé pour notre serveur JSON, la commande rackup. Mais il y a plus! Le bijou de rack fournit de petites applications pour de nombreux cas d'utilisation, comme le traitement de fichiers statiques ou même de répertoires entiers. Voyons comment nous servons un fichier simple, par exemple un fichier HTML très basique situé à l'adresse htmls/index.html:
<!DOCTYPE HTML>
<html>
<head>
<title>The Index</title>
</head>
<body>
<p>Index Page</p>
</body>
</html>
Nous souhaitons peut-être servir ce fichier à partir de la racine du site, ajoutons ce qui suit à notre fichier 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 fichier 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 maintenant inclure 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 pleinement compris qu'après avoir travaillé à la création de ce miniature Ruby =) ==. J'ai partagé mes connaissances sur Rack (sous la forme d'une histoire) ici mon blog: http://gauravchande.com/what-is-rack-in-Ruby-Rails
Les commentaires sont plus que bienvenus.
config.ru
exemple minimal exécutable
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)
Exécutez rackup
et visitez localhost:9292
. La sortie est:
main
Middleware
Il est donc clair que le Middleware
enveloppe et appelle l'application principale. Par conséquent, il est capable de pré-traiter la demande et de post-traiter la réponse de quelque manière que ce soit.
Comme expliqué à: http://guides.rubyonrails.org/Rails_on_rack.html#action-dispatcher-middleware-stack , Rails utilise des middlewares Rack pour beaucoup de c'est la fonctionnalité, et vous pouvez ajouter votre propre aussi avec config.middleware.use
méthodes familiales.
L’avantage d’implémenter des fonctionnalités dans un middleware réside dans le fait que vous pouvez le réutiliser sur n’importe quel framework Rack, donc tous les principaux Ruby), et pas seulement Rails.
Le middleware en rack est un moyen de filtrer une requête et une réponse entrant dans votre application. Un composant middleware situé entre le client et le serveur traite les demandes entrantes et les réponses sortantes, mais constitue plus qu'une interface pouvant être utilisée pour communiquer avec le serveur Web. Il est utilisé pour grouper et commander des modules, qui sont généralement des classes Ruby), et spécifient leurs dépendances. Le module de middleware Rack doit uniquement: call ”, qui prend le hachage de l'environnement comme paramètre. La valeur renvoyée par cet appel est un tableau contenant: le code d'état, le hachage de l'environnement et le corps de la réponse.
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 Environment (un hachage, contenu dans une requête HTTP d'un client, composée d'en-têtes de type CGI) à votre application 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 méthode #call
Avec le hachage d'environnement comme paramètre (généralement défini comme env
). #call
Doit renvoyer un tableau contenant exactement trois valeurs:
each
).Vous pouvez écrire une application Rack qui renvoie un tel tableau. Celui-ci sera renvoyé à votre client, par Rack, dans un Réponse (ce sera en fait un instance de la classe Rack::Response
[cliquez pour aller à la documentation]).
gem install rack
config.ru
- Rack sait le rechercher.Nous allons créer une petite application Rack qui renvoie une réponse (une instance de Rack::Response
) Dont le corps de la réponse est un tableau contenant une chaîne: "Hello, World!"
.
Nous allons lancer un serveur local en utilisant la commande rackup
.
Lors de la visite du port correspondant dans notre navigateur, nous verrons "Hello, World!" 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
Lancez un serveur local avec rackup
et visitez localhost: 9292 et vous devriez voir 'Hello, World!' rendu.
Ceci n’est pas une explication complète, mais ce qui se passe ici c’est que le client (le navigateur) envoie une requête HTTP à Rack, via votre serveur local, qui instancie MessageApp
et exécute call
. passer le hachage d'environnement en tant que paramètre dans la méthode (l'argument env
).
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 magic pour imprimer 'Hello, World!' à l'écran.
Incidemment, si vous voulez voir à quoi ressemble le hachage de l'environnement, il suffit de mettre puts env
Sous def call(env)
.
Minimal, ce que vous avez écrit ici est une application Rack!
Dans notre petite application Rack, nous pouvons interagir avec le hachage env
(voir ici pour plus d'informations sur le hachage Environment).
Nous allons implémenter la possibilité pour l'utilisateur de saisir sa propre chaîne de requête dans l'URL. Cette chaîne sera donc présente dans la requête HTTP, encapsulée sous forme de valeur dans l'une des paires clé/valeur du hachage Environment.
Notre application Rack accédera à cette chaîne de requête à partir du hachage Environment et la renverra au client (notre navigateur, dans ce cas) via le corps de la réponse.
À partir des documents de rack sur le hachage d’environnement: "QUERY_STRING: la partie de l’URL de demande qui suit le?, S’il en existe. 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 "hello" affiché dans la fenêtre d'affichage.
Nous allons:
MessageSetter
,env
,MessageSetter
insérera une clé 'MESSAGE'
dans la valeur de hachage env, sa valeur étant 'Hello, World!'
si env['QUERY_STRING']
est vide; env['QUERY_STRING']
Sinon,@app.call(env)
- @app
étant la prochaine application de la 'pile': MessageApp
.Tout d'abord, la version "à main 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
Dans la documentation Rack :: Builder , nous voyons que Rack::Builder
Implémente un petit DSL pour construire de manière itérative des applications Rack. Cela signifie que vous pouvez créer une "pile" composée d'un ou plusieurs middlewares et d'une application de "niveau inférieur" à expédier. Toutes les demandes adressées à votre application de niveau inférieur seront d'abord traitées par votre (vos) middleware (s).
#use
Spécifie le middleware à utiliser dans une pile. Il prend le middleware comme argument.
Le middleware en rack doit:
call
qui prend le hachage de l'environnement en tant que paramètre. Dans notre cas, le "middleware" est MessageSetter
, le "constructeur" est la méthode de MessageSetter initialize
, la "prochaine application" dans la pile est MessageApp
.
Donc ici, à cause de ce que Rack::Builder
Fait sous le capot, l'argument app
de MessageSetter
de initialize
est MessageApp
.
(faites le tour du dessus avant de passer à autre chose)
Par conséquent, chaque pièce de middleware "transmet" le hachage d'environnement existant à la prochaine application de la chaîne. Vous avez donc la possibilité de transformer ce hachage d'environnement dans le middleware avant de le transmettre à la prochaine application de la pile.
#run
Prend un argument qui est un objet qui répond à #call
Et renvoie une réponse en rack (une instance de Rack::Response
).
En utilisant Rack::Builder
, Vous pouvez construire des chaînes de Middlewares et toute requête adressée à 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" constitué de plusieurs Middlewares traitant de sujets tels que:
(points de balle ci-dessus d'une autre réponse sur ce fil)
Vous verrez souvent cela dans les applications professionnelles de Sinatra. Sinatra utilise Rack! Voir ici pour la définition de ce que Sinatra [~ # ~] est [~ # ~]!
Pour terminer, 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 que MessageApp
est en train de faire, voici sa version à main longue qui montre explicitement que #call
Crée une nouvelle instance de Rack::Response
, Avec le trois arguments.
class MessageApp
def call(env)
Rack::Response.new([env['MESSAGE']], 200, {})
end
end
J'ai utilisé le middleware Rack pour résoudre quelques problèmes:
Cela apportait des solutions assez élégantes dans les deux cas.
Rack - L'interface Web et serveur d'applications noir et blanc
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 de middleware entre le serveur Web et l'application pour modifier le traitement de votre demande/réponse. Le composant middleware se situe entre le client et le serveur et traite les demandes entrantes et les réponses sortantes.
En termes simples, il s’agit en fait d’un ensemble de directives sur la manière dont un serveur et une Rails (ou toute autre application Ruby Web)) devrait se parler.
Pour utiliser Rack, fournissez une "application": un objet qui répond à la méthode d'appel, prenant le hachage de l'environnement comme paramètre et renvoyant un tableau contenant 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 Rails, nous avons config.ru en tant que fichier rack, vous pouvez exécuter n’importe quel fichier rack avec la commande rackup
. 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. Commande permettant d'exécuter le fichier rack sur un port spécifique est
rackup -p PORT_NUMBER