Place aux middlewares avec rack

Publié le 4 janvier 2011 par Martin Catty | back

Cet article est publié sous licence CC BY-NC-SA

Créer son middleware

Dans mon introduction à rack je vous ai laissé sur votre faim concernant la création de middleware.

Nous allons donc voir comment faire un mini-logger.

Tout d’abord ne pas oublier de rajouter le dossier lib dans l’autoload:

  module App
    class Application < Rails::Application
      config.autoload_paths += %W(#{config.root}/lib)
    end
  end

Ensuite on crée une class Tracking dans notre dossier lib:

  class Tracking
    def initialize(app)
      @app = app
    end

    def call(env)
      request = Rack::Request.new(env)
      File.open(File.join(Rails.root, "log", "custom.log"), "a") do |f|
       f << "IP: #{request.ip}, PATH: #{request.path}, PARAMS: #{request.params}\n"
      end
      @app.call(env)
    end
  end

Et voilà notre middleware d’une 10aine de ligne.

Quelques explications:

  • Le constructeur se contente de placer l’objet app dans une variable d’instance pour pouvoir y accéder depuis call.
  • call prend l’environnement en entrée. Les middlewares sont chaînés, ce qui permet de modifier l’environnement d’un middleware à l’autre.

Par exemple je pourrai définir env[‘foo’] = ‘bar’ et y avoir accès dans le middleware suivant.

L’invocation de app.call(env) va renvoyer le status de la requête (code http, ex: 200), les entêtes et la réponse. Le middleware peut donc modifier ces 3 éléments.

Ici nous nous contentons de parser la requête à partir de l’environnement entrant à l’aide de rack. Ensuite nous avons accès à toutes les méthodes classiques de request.

Inclure son middleware

Dans notre fichier d’environnement, par exemple developement.rb il suffit de définir les middlewares de la sorte:

  App::Application.configure do
    config.middleware.delete("Rails::Rack::Logger")
    config.middleware.insert_after("ActionDispatch::ParamsParser", "Tracking")
    # config.middleware.use("Tracking")
  end

Il est important de bien utiliser les versions quotées des classes. L’utilisation de la constante entrainerait une exception, car la classe ne serait pas encore chargée.

On a ici commencé par retirer le logger de rack pour ajouter notre middleware après le parser. La dernière ligne montre l’utilisation de use, qui permet d’ajouter le middleware en fin de liste.

On remarque que Rails 3 est vraiment modulable, l’ajout ou la suppression d’un middleware est très simple, rendant le framework très souple.

Analyse d’un middleware existant: Etag

Bon, notre middleware est vraiment basique, mais vous allez voir qu’un middleware plus pratique tel qu’Etag ne fait pas grand chose de plus compliqué.

  require 'digest/md5'

  module Rack
    # Automatically sets the ETag header on all String bodies
    class ETag
      def initialize(app)
        @app = app
      end

      def call(env)
        status, headers, body = @app.call(env)

        if !headers.has_key?('ETag')
          digest, body = digest_body(body)
          headers['ETag'] = %("#{digest}")
        end

        [status, headers, body]
      end

      private
        def digest_body(body)
          digest = Digest::MD5.new
          parts = []
          body.each do |part|
            digest << part
            parts << part
          end
          [digest.hexdigest, parts]
        end
    end
  end

L’initialisation est identique. Le call va récupérer le status, les entêtes et la requête.

Ensuite le contenu de la requête est hashé et un entête Etag avec le checksum MD5 ajouté, ce qui permettra au serveur de servir directement le contenu s’il a déjà été calculé.

Et c’est tout !

L’équipe Synbioz.

Libres d’être ensemble.