web-dev-qa-db-fra.com

Qu'est-ce qu'un middleware Rack?

Qu'est-ce que le middleware Rack en Ruby? Je ne pouvais pas trouver une bonne explication à ce qu'ils entendent par "middleware".

260
chrisgoyal

Rack comme Design

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.

Communauté

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 .

Qu'est-ce qu'un 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.

Plus d'information

338
Chris McCauley

Tout d’abord, Rack, c’est exactement deux choses:

  • Une convention d'interface de serveur Web
  • Une gemme

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ù.

70
Thomas Fankhauser

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.

19
Gaurav Chande

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.

6
L.Cole

Qu'est ce que Rack?

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.

Qu'est-ce qu'une application en rack?

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:

  • le code d'état (par exemple, "200"),
  • un hachage d'en-têtes ,
  • le corps de réponse (qui doit répondre à la méthode Ruby, 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]).

Une application de rack très simple:

  • gem install rack
  • Créez un fichier 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!

Faire une application rack interagir avec le hachage d'environnement entrant

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.

Middleware de rack

Nous allons:

  • insérer un morceau de Rack Middleware dans notre base de code - une classe: MessageSetter,
  • le hachage Environment atteindra cette classe en premier et sera passé en paramètre: 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,
  • enfin, il retournera @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:

  • avoir un constructeur qui prend l'application suivante dans la pile en tant que paramètre.
  • répondez à la méthode 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).

Conclusions

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:

  • Authentification
  • Autorisation
  • Mise en cache
  • Décoration
  • Surveillance des performances et de l'utilisation
  • Exécution (traite réellement la demande et fournit une réponse)

(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

Liens utiles

4
Andy

J'ai utilisé le middleware Rack pour résoudre quelques problèmes:

  1. Correction des erreurs d'analyse JSON avec un middleware Rack personnalisé et retour des messages d'erreur correctement formatés lorsque le client soumet JSON décomposé
  2. Compression de contenu via Rack :: Deflater

Cela apportait des solutions assez élégantes dans les deux cas.

4
djcp

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:

  • Le code de réponse HTTP
  • n hachage d'en-têtes
  • Le corps de la réponse, qui doit répondre à chaque demande.

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
0
V K Singh