Pregunta

¿Qué es el middleware Rack en Ruby?No pude encontrar ninguna buena explicación de lo que quieren decir con "middleware".

¿Fue útil?

Solución

Estante como diseño

El middleware de rack es más que "una forma de filtrar una solicitud y una respuesta": es una implementación del patrón de diseño de tubería para servidores web que utilizan Estante.

Separa muy claramente las diferentes etapas del procesamiento de una solicitud; la separación de preocupaciones es un objetivo clave de todos los productos de software bien diseñados.

Por ejemplo, con Rack puedo tener etapas separadas del proceso haciendo:

  • Autenticación:Cuando llega la solicitud, ¿son correctos los detalles de inicio de sesión de los usuarios?¿Cómo valido este OAuth, autenticación básica HTTP, nombre/contraseña?

  • Autorización:"¿Está el usuario autorizado a realizar esta tarea en particular?", es decirseguridad basada en roles.

  • Almacenamiento en caché:¿Ya procesé esta solicitud? ¿Puedo devolver un resultado almacenado en caché?

  • Decoración:¿Cómo puedo mejorar la solicitud para mejorar el procesamiento posterior?

  • Monitoreo de rendimiento y uso:¿Qué estadísticas puedo obtener de la solicitud y la respuesta?

  • Ejecución:realmente manejar la solicitud y proporcionar una respuesta.

Poder separar las diferentes etapas (y opcionalmente incluirlas) es de gran ayuda para desarrollar aplicaciones bien estructuradas.

Comunidad

También se está desarrollando un gran ecosistema en torno al Rack Middleware: debería poder encontrar componentes de rack prediseñados para realizar todos los pasos anteriores y más.Ver la wiki de Rack GitHub para obtener una lista de middleware.

¿Qué es el middleware?

Middleware es un término terrible que se refiere a cualquier componente/biblioteca de software que ayuda con la ejecución de alguna tarea, pero que no participa directamente en ella.Ejemplos muy comunes son el registro, la autenticación y otros. componentes de procesamiento horizontales comunes.Estas tienden a ser las cosas que todos necesitan en múltiples aplicaciones, pero no mucha gente está interesada (o debería estar) en construirlas ellos mismos.

Más información

Otros consejos

En primer lugar, Porta es exactamente dos cosas:

  • Una convención interfaz de servidor web
  • Una joya

Rack - El servidor web de la interfaz

Los conceptos básicos de la rejilla es una simple convención. Cada servidor web compatible con cremallera siempre llamar a un método de llamada en un objeto le das y servir al resultado de ese método. Estante especifica exactamente cómo este método llamado tiene que parecer, y lo que tiene que volver. Eso es bastidor.

Vamos a darle un simple intento. Voy a usar WEBrick como servidor web compatible con cremallera, pero ninguno de ellos lo hará. Vamos a crear una sencilla aplicación web que devuelve una cadena JSON. Para ello vamos a crear un archivo llamado config.ru. El config.ru llamaría automáticamente por rackup comando de la gema estante que se limitará a ejecutar el contenido del config.ru en un servidor web compatible con cremallera. Así que vamos a añadir lo siguiente al archivo config.ru:

class JSONServer
  def call(env)
    [200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
  end
end

map '/hello.json' do
  run JSONServer.new
end

A medida que la Convención especifica nuestro servidor tiene una llamada llamada al método que acepta un hash de medio ambiente y devuelve una matriz con la forma [de estado, cabeceras, cuerpo] para el servidor web para servir. Vamos a intentarlo a cabo simplemente llamando rackup. Un servidor compatible con cremallera por defecto, tal vez WEBrick o mestizo se iniciará inmediatamente y esperar a que las solicitudes para 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

Vamos a probar nuestro nuevo servidor JSON por cualquiera de curling o visitar el http://localhost:9292/hello.json url y listo:

$ curl http://localhost:9292/hello.json
{ message: "Hello!" }

Funciona. ¡Excelente! Esa es la base para cada framework web, ya sea carriles o Sinatra. En algún momento se implementan un método de llamada, trabajar a través de todo el código marco, y, finalmente, devolver una respuesta en el típico [estado, cabeceras, cuerpo] formulario.

En Ruby on Rails, por ejemplo, las solicitudes de rack golpea la clase ActionDispatch::Routing.Mapper que se parece a esto:

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

Así que, básicamente Carriles cheques, dependiendo de si el hash env cualquier coincidencia de ruta. Si por lo que pasa el hash env a la aplicación para calcular la respuesta, de lo contrario, responde de inmediato con un Así que cualquier servidor web 404. que es es compatible con la convención de interfaz de rack, es capaz de servir una aplicación Rails completamente soplado.

Middleware

Rack también apoya la creación de capas de middleware. Básicamente interceptar una petición, hacer algo con ella y transmitirla. Esto es muy útil para tareas versátiles.

Supongamos que queremos añadir a nuestro servidor de registro JSON que también mide el tiempo que tarda una solicitud. Podemos simplemente crear un registrador de middleware que hace exactamente esto:

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

Cuando se crea, guarda en sí mismo una copia de la solicitud de rack real. En nuestro caso eso es una instancia de nuestra JSONServer. Estante automáticamente llama al método llamada en el middleware y espera volver una matriz [status, headers, body], al igual que nuestros rendimientos JSONServer.

Así que en este middleware, se toma el punto de inicio, a continuación, la llamada real a la JSONServer se hace con @app.call(env), entonces el registrador da salida a la entrada de registro y finalmente devuelve la respuesta [@status, @headers, @body].

Para hacer que nuestro pequeño rackup.ru utilizar este middleware, añadir un RackLogger uso de esta manera:

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   

Reiniciar el servidor y listo, se da salida a un registro en cada petición. Estante le permite añadir múltiples middleware que son llamados en el orden en que se añaden. Es sólo una gran manera de añadir funcionalidad sin cambiar el núcleo de la aplicación de bastidor.

Rack - La gema

A pesar de cremallera - en primer lugar - es una convención que también es una joya que ofrece una gran funcionalidad. Uno de ellos ya hemos utilizado para nuestro servidor JSON, el comando rackup. Pero aún hay más! La gema de parrilla ofrece pequeñas aplicaciones para una gran cantidad de casos de uso, como servir archivos estáticos o incluso directorios completos. Vamos a ver cómo se sirve un simple archivo, por ejemplo, un archivo HTML muy básico ubicado en htmls / index.html:

<!DOCTYPE HTML>
  <html>
  <head>
    <title>The Index</title>
  </head>

  <body>
    <p>Index Page</p>
  </body>
</html>

tal vez queremos servir a este archivodesde la raíz del sitio Web, por lo que vamos a añadir lo siguiente a nuestra config.ru:

map '/' do
  run Rack::File.new "htmls/index.html"
end

Si nos visita http://localhost:9292 vemos nuestro archivo html perfectamente prestados. Eso era de fácil, ¿verdad?

Vamos a añadir un directorio completo de archivos javascript mediante la creación de algunos archivos javascript en / javascript y añadiendo lo siguiente a la config.ru:

map '/javascripts' do
  run Rack::Directory.new "javascripts"
end

Reiniciar el servidor y la visita http://localhost:9292/javascript y verá una lista de todos los archivos JavaScript se puede incluir ahora directamente desde cualquier lugar.

Yo tenía un problema de comprensión rack misma por una buena cantidad de tiempo. Sólo he entendido completamente después de trabajar en la fabricación de este miniatura servidor web Rubí mí mismo. He compartido mis aprendizajes sobre bastidor (en la forma de una historia) aquí en mi blog: http://gauravchande.com/what-is-rack-in-ruby-rails

La retroalimentación es más que bienvenido.

config.ru ejemplo runnable mínimo

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)

Ejecutar rackup y visitar localhost:9292. La salida es:

main
Middleware

Por lo tanto, es evidente que la envuelve Middleware y llama a la aplicación principal. Por lo tanto es capaz de pre-proceso de la solicitud, y post-proceso de la respuesta de ninguna manera.

Como se explica en: http://guides.rubyonrails.org /rails_on_rack.html#action-dispatcher-middleware-stack , Rails utiliza middleware rack para una gran cantidad de su funcionalidad, y se puede añadir es el propietario también con métodos familiares config.middleware.use.

La ventaja de implementar la funcionalidad en un middleware es que se puede reutilizar en cualquier marco de bastidor, por lo tanto, todos los principales Ruby, y no sólo rieles.

middleware Rack es una forma de filtrar la petición y la respuesta que llega a su aplicación. Un componente middleware se encuentra entre el cliente y el servidor, el procesamiento de las solicitudes entrantes y salientes respuestas, pero es más de interfaz que puede ser usado para referirse al servidor web. Se utiliza para agrupar los módulos y el orden, que suelen ser las clases de Ruby, y especificar la dependencia entre ellos. módulo de middleware estante solamente debe: - tiene constructor que toma la próxima aplicación en la pila como parámetro - responde a “llamar” método, que toma entorno de hash como parámetro. Volviendo valor de esta llamada es una matriz de:. Código de estado, el medio ambiente y el cuerpo de la respuesta de hash

He Rack middleware utilizado para resolver un par de problemas:

  1. La captura de errores de análisis JSON con el middleware de encargo del estante y devolver mensajes de error con un formato agradable cuando cliente envía reventado JSON
  2. compresión de contenido a través de rack :: deflactor

Se proporcionó correcciones bastante elegante en ambos casos.

¿Qué es el estante?

Rack proporciona una interfaz mínima entre servidores web que admiten Ruby y Ruby frameworks.

Usando Rack puedes escribir una aplicación Rack.

Rack pasará el hash del entorno (un hash, contenido dentro de una solicitud HTTP de un cliente, que consta de encabezados tipo CGI) a su aplicación Rack, que puede usar los elementos contenidos en este hash para hacer lo que quiera.

¿Qué es una aplicación en rack?

Para utilizar Rack, debe proporcionar una 'aplicación', un objeto que responde a la #call método con el Hash de entorno como parámetro (normalmente definido como env). #call debe devolver una matriz de exactamente tres valores:

  • el Código de estado (por ejemplo, '200'),
  • a Hash de encabezados,
  • el Cuerpo de respuesta (que debe responder al método Ruby, each).

Puede escribir una aplicación Rack que devuelva dicha matriz; Rack la enviará de regreso a su cliente, dentro de un Respuesta (esto en realidad será un instancia de la clase Rack::Response [haga clic para ir a documentos]).

Una aplicación de bastidor muy sencilla:

  • gem install rack
  • Crear un config.ru archivo: Rack sabe buscar esto.

Crearemos una pequeña aplicación en rack que devuelve una respuesta (una instancia de Rack::Response) cuyo cuerpo de respuesta es una matriz que contiene una cadena: "Hello, World!".

Activaremos un servidor local usando el comando rackup.

Al visitar el puerto relevante en nuestro navegador, veremos "¡Hola, mundo!" Renderizado en la ventana gráfica.

#./message_app.rb
class MessageApp
  def call(env)
    [200, {}, ['Hello, World!']]
  end
end

#./config.ru
require_relative './message_app'

run MessageApp.new

Encienda un servidor local con rackup y visita servidor local: 9292 Y deberías ver '¡Hola, mundo!' representado.

Esta no es una explicación completa, pero esencialmente lo que sucede aquí es que el Cliente (el navegador) envía una Solicitud HTTP a Rack, a través de su servidor local, y Rack crea una instancia. MessageApp y corre call, pasando el Hash de entorno como parámetro al método (el env argumento).

Rack toma el valor de retorno (la matriz) y lo usa para crear una instancia de Rack::Response y lo envía de vuelta al Cliente.El navegador utiliza magia para imprimir '¡Hola, mundo!' a la pantalla.

Por cierto, si quieres ver cómo se ve el hash del entorno, simplemente pon puts env debajo def call(env).

Por mínimo que sea, ¡lo que has escrito aquí es una aplicación Rack!

Hacer que una aplicación en rack interactúe con el hash del entorno entrante

En nuestra pequeña aplicación Rack, podemos interactuar con el env hash (ver aquí para obtener más información sobre el hash ambiental).

Implementaremos la capacidad para que el usuario ingrese su propia cadena de consulta en la URL, por lo tanto, esa cadena estará presente en la solicitud HTTP, encapsulada como un valor en uno de los pares clave/valor del hash del entorno.

Nuestra aplicación Rack accederá a esa cadena de consulta desde el hash del entorno y la enviará de vuelta al cliente (nuestro navegador, en este caso) a través del cuerpo de la respuesta.

De los documentos de Rack sobre Environment Hash:"QUERY_STRING:La parte de la URL de solicitud que sigue a ?, si corresponde.¡Puede estar vacío, pero siempre es necesario!"

#./message_app.rb
class MessageApp
  def call(env)
    message = env['QUERY_STRING']
    [200, {}, [message]]
  end
end

Ahora, rackup y visita localhost:9292?hello (?hello siendo la cadena de consulta) y debería ver 'hola' representado en la ventana gráfica.

Middleware de bastidor

Lo haremos:

  • inserte una pieza de Rack Middleware en nuestro código base: una clase: MessageSetter,
  • El hash del entorno llegará primero a esta clase y se pasará como parámetro: env,
  • MessageSetter insertará un 'MESSAGE' clave en el hash env, siendo su valor 'Hello, World!' si env['QUERY_STRING'] esta vacio; env['QUERY_STRING'] si no,
  • finalmente regresará @app.call(env) - @app siendo la siguiente aplicación en la 'Stack': MessageApp.

Primero, la versión 'larga':

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

Desde el Rack::Documentos del generador vemos eso Rack::Builder implementa un pequeño DSL para construir iterativamente aplicaciones Rack.Básicamente, esto significa que puede crear una 'pila' que consta de uno o más middlewares y una aplicación de 'nivel inferior' para enviar.Todas las solicitudes que lleguen a su aplicación de nivel inferior serán procesadas primero por su(s) Middleware(s).

#use especifica el middleware que se utilizará en una pila.Toma el middleware como argumento.

El middleware de bastidor debe:

  • tener un constructor que tome la siguiente aplicación en la pila como parámetro.
  • responder a la call método que toma el hash del entorno como parámetro.

En nuestro caso, el 'Middleware' es MessageSetter, el 'constructor' es de MessageSetter initialize método, la 'siguiente aplicación' en la pila es MessageApp.

Entonces aquí, por qué Rack::Builder hace debajo del capó, el app argumento de MessageSetter's initialize el método es MessageApp.

(Entienda lo anterior antes de continuar)

Por lo tanto, cada pieza de Middleware esencialmente "transmite" el hash del entorno existente a la siguiente aplicación de la cadena, por lo que tiene la oportunidad de mutar ese hash del entorno dentro del Middleware antes de pasarlo a la siguiente aplicación de la pila.

#run toma un argumento que es un objeto que responde a #call y devuelve una respuesta de rack (una instancia de Rack::Response).

Conclusiones

Usando Rack::Builder puede construir cadenas de Middlewares y cualquier solicitud a su aplicación será procesada por cada Middleware por turno antes de ser procesada finalmente por la pieza final de la pila (en nuestro caso, MessageApp).Esto es extremadamente útil porque separa las diferentes etapas del procesamiento de solicitudes.En términos de 'separación de preocupaciones', ¡no podría ser mucho más limpio!

Puede construir una 'canalización de solicitudes' que consta de varios Middlewares que se ocupan de cosas como:

  • Autenticación
  • Autorización
  • Almacenamiento en caché
  • Decoración
  • Monitoreo de rendimiento y uso
  • Ejecución (realmente manejar la solicitud y proporcionar una respuesta)

(arriba de viñetas de otra respuesta en este hilo)

Verá esto a menudo en aplicaciones profesionales de Sinatra.¡Sinatra usa Rack!Ver aquí para la definición de lo que Sinatra ES!

Como nota final, nuestra config.ru se puede escribir en un estilo abreviado, produciendo exactamente la misma funcionalidad (y esto es lo que normalmente verá):

require_relative './message_app'
require_relative './middleware/message_setter'

use MessageSetter
run MessageApp.new

Y para mostrar más explícitamente lo que MessageApp está haciendo, aquí está su versión 'manual' que muestra explícitamente que #call está creando una nueva instancia de Rack::Response, con los tres argumentos requeridos.

class MessageApp
  def call(env)
    Rack::Response.new([env['MESSAGE']], 200, {})
  end
end

Enlaces útiles

Rack: la interfaz entre el servidor web y de aplicaciones

Rack es un paquete Ruby que proporciona una interfaz para que un servidor web se comunique con la aplicación.Es fácil agregar componentes de middleware entre el servidor web y la aplicación para modificar la forma en que se comporta su solicitud/respuesta.El componente de middleware se encuentra entre el cliente y el servidor y procesa las solicitudes entrantes y las respuestas salientes.

En palabras simples, es básicamente solo un conjunto de pautas sobre cómo un servidor y una aplicación Rails (o cualquier otra aplicación web Ruby) deben comunicarse entre sí..

Para utilizar Rack, proporcione una "aplicación":un objeto que responde al método de llamada, tomando como parámetro el hash del entorno y devolviendo un Array con tres elementos:

  • El código de respuesta HTTP
  • Un hash de encabezados
  • El cuerpo de respuesta, que debe responder a cada pedido.

Para obtener más explicaciones, puede seguir los enlaces a continuación.

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

En Rails, tenemos config.ru como un archivo rack, puedes ejecutar cualquier archivo rack con rackup dominio.Y el puerto predeterminado para esto es 9292.Para probar esto, simplemente puede ejecutar rackup en su directorio de rieles y vea el resultado.También puedes asignar el puerto en el que deseas ejecutarlo.El comando para ejecutar el archivo rack en cualquier puerto específico es

rackup -p PORT_NUMBER
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top