Qu'est-ce que le middleware Rack?


267

Qu'est-ce que le middleware Rack dans Ruby? Je n'ai trouvé aucune bonne explication de ce qu'ils entendent par "middleware".


4
Il existe également un guide sur RailsGuide couvrant désormais Rack de manière complète, y compris le middleware: guides.rubyonrails.org/rails_on_rack.html
xji

Merci beaucoup à l'équipe PhusionPassenger, ils ont un article bien expliqué sur leur blog. rubyraptor.org/…
Lamian

Rack et middleware de rack sont expliqués dans CET article. Également expliqué sur la création d'une application basée sur un rack.
shashwat srivastava

Réponses:


353

Rack comme conception

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.

Communauté

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 .

Qu'est-ce que le 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.

Plus d'information


Une chose sur laquelle je ne suis pas clair: tous les middleware partagent-ils les mêmes données? Est-il possible de les séparer (c'est-à-dire un bac à sable) pour des raisons de sécurité?
Brian Armstrong

2
Rack fait partie de votre application, donc tous les middleware composent la même copie de la demande et chacun peut la modifier comme bon lui semble. AFAIK, il n'y a aucun moyen de les sandboxer de la même manière, il n'y a aucun moyen de sandboxer un objet d'un autre dans le même processus (malgré les tentatives de sandboxing Ruby).
Chris McCauley

1
et Comprenez que Rack est différent de Rake.
Manish Shrivastava

1
J'aime penser que le middleware est tout ce qui se trouve au milieu de mon application entre ce que j'ai codé et ce qui va vers et depuis mon serveur ... qui est hébergé sur rackspace. La raison pour laquelle le terme «middleware rack» prête à confusion, comme nous le savons tous, est parce que c'est Confucius qui a écrit tout le middleware rack original, il y a plus de 2000 ans. En France.
LpLrich

74

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

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

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.jsonet 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.Mapperclasse 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:9292nous 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/javascriptet vous verrez une liste de tous les fichiers javascript que vous pouvez inclure maintenant directement de n'importe où.


3
Mais pas le middleware Rack?
Rup

1
Si vous ne savez pas ce qu'est un rack, vous saurez exactement de quoi il s'agit et comment l'utiliser après avoir lu ce billet de blog. Très agréable. Ironiquement, cependant, le lien vers la documentation officielle du rack à la fin de l'article n'est plus disponible!
Colin

Votre droit, merci. J'ai inclus le contenu dans le message et supprimé le lien mort.
Thomas Fankhauser

Je dirais que ce n'est pas une convention. c'est une interface, un contrat bien défini pour un modèle requête-réponse
Ron Klein

20

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.


13
Les réponses de lien uniquement sont déconseillées sur Stack Overflow , car si la ressource vers laquelle le lien va devient indisponible à l'avenir, la réponse devient inutile. Veuillez au moins résumer les points pertinents de votre article de blog et les ajouter à cette réponse.

Merci pour votre message. Je suis un programmeur Rails très débutant et j'ai compris le concept de rack avec votre message clair.
Eduardo Ramos

Excellent article de blog. Les autres réponses semblent un peu plus compliquées OMI.
Clam

Quelle explication impressionnante. Merci, Gaurav.
rovitulli

7

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 rackupet visitez localhost:9292. La sortie est:

main
Middleware

Il est donc clair que le Middlewarewraps 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.usedes 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.


6

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.


4

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

  1. Détection des erreurs d'analyse JSON avec un middleware de rack personnalisé et renvoi de messages d'erreur bien formatés lorsque le client soumet un JSON interrompu
  2. Compression de contenu via Rack :: Deflater

Il offrait des correctifs assez élégants dans les deux cas.


2
Cette réponse, bien que quelque peu utile, ne répond pas réellement à la question de savoir ce qu'est le middleware de rack .

Aussi celle-ci est une réponse assez liée uniquement aux liens ...: P
Smar

4

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

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

Pour utiliser Rack, vous devez fournir une «application» - un objet qui répond à la #callméthode avec le hachage d'environnement comme paramètre (généralement défini comme env). #calldoit renvoyer un tableau d'exactement trois valeurs:

  • le code d'état (par exemple «200»),
  • une 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 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]).

Une application en rack très simple:

  • gem install rack
  • Créez un config.rufichier - 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 rackupet 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 MessageAppet 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::Responseet 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 envdessousdef call(env) .

Aussi minime soit-elle, ce que vous avez écrit ici est une application Rack!

Faire interagir une application en rack avec le hachage de l'environnement entrant

Dans notre petite application Rack, nous pouvons interagir avec le envhachage (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, rackupet visitez localhost:9292?hello( ?helloétant la chaîne de requête) et vous devriez voir «bonjour» rendu dans la fenêtre.

Middleware de rack

Nous allons:

  • insérer un morceau de Rack Middleware dans notre base de code - une classe: MessageSetter ,
  • le hachage d'environnement frappera cette classe en premier et sera transmis en tant que paramètre: env ,
  • MessageSetterinsérera une 'MESSAGE'clé dans le hachage env, sa valeur étant 'Hello, World!'si env['QUERY_STRING']est vide;env['QUERY_STRING']si non,
  • enfin, il sera de retour @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.

#usespécifie le middleware à utiliser dans une pile. Il prend le middleware comme argument.

Le middleware de rack doit:

  • avoir un constructeur qui prend la prochaine application dans la pile comme paramètre.
  • répondre à la callméthode qui prend le hachage d'environnement comme paramètre.

Dans notre cas, le «middleware» est MessageSetter, le «constructeur» est la initializeméthode de MessageSetter , la «prochaine application» dans la pile estMessageApp .

Donc ici, à cause de ce qui se Rack::Builderpasse sous le capot, l' appargument de MessageSetterla initializemé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.

#runprend un argument qui est un objet qui répond #callet renvoie une réponse de rack (une instance de Rack::Response).

Conclusions

En utilisant Rack::Buildervous 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:

  • Authentification
  • Autorisation
  • Mise en cache
  • Décoration
  • Surveillance des performances et de l'utilisation
  • Exécution (en fait gérer la demande et fournir une réponse)

(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.rupeut ê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 MessageAppse passe, voici sa version «longue» qui montre explicitement que #callcré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

Liens utiles


1

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:

  • Le code de réponse HTTP
  • Un hachage d'en-têtes
  • L' organisme de 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 les rails, nous avons config.ru comme fichier rack, vous pouvez exécuter n'importe quel fichier rack avec la rackupcommande. Et le port par défaut pour cela est 9292. Pour tester cela, vous pouvez simplement exécuter rackupdans 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

1

image montrant le rack entre la licorne et les rails

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.