started rails rackup getting app and ruby-on-rails ruby http web-applications rack

ruby on rails - rails - ¿Qué es el middleware Rack?



ruby and rails documentation (7)

¿Qué es Rack?

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

Usando Rack puedes escribir una aplicación de Rack.

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

¿Qué es una aplicación de rack?

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

  • el código de estado (por ejemplo, ''200''),
  • un hash de encabezados ,
  • el Cuerpo de Respuesta (que debe responder al método Ruby, each ).

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

Una aplicación de rack muy simple:

  • gem install rack
  • Cree un archivo config.ru - Rack sabe que debe buscar esto.

Crearemos una pequeña Aplicación de Rack que devuelve una Respuesta (una instancia de Rack::Response ) cuyo Cuerpo de Respuesta es una matriz que contiene una Cadena: "Hello, World!" .

rackup un servidor local usando el comando rackup .

Cuando visitemos el puerto correspondiente en nuestro navegador, veremos "¡Hola, mundo!" prestados 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

rackup un servidor local con rackup y visite localhost: 9292 y debería ver ''¡Hola, mundo!'' rendido

Esta no es una explicación completa, pero básicamente lo que sucede aquí es que el Cliente (el navegador) envía una Solicitud de HTTP a Rack, a través de su servidor local, y Rack MessageApp instancia de MessageApp y ejecuta la call , pasando el Hash del entorno como un parámetro a la Método (el argumento env ).

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 la magic para imprimir ''¡Hola, mundo!'' a la pantalla.

Por cierto, si desea ver cómo se ve el hash del entorno, simplemente coloque los puts env debajo de la def call(env) .

¡Mínimo como es, lo que has escrito aquí es una aplicación de Rack!

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

En nuestra aplicación Little Rack, podemos interactuar con el hash env (vea here para obtener más información sobre el hash del ambiente).

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 en la Respuesta.

De los documentos de Rack en el hash de entorno: "QUERY_STRING: la parte de la URL de solicitud que sigue al?, 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 visite localhost:9292?hello ( ?hello es la cadena de consulta) y debería ver "hola" en la ventana gráfica.

Rack Middleware

Lo haremos:

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

Primero, la versión ''mano 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

De los documentos de Rack :: Builder vemos que Rack::Builder implementa un DSL pequeño para construir iterativamente aplicaciones de Rack. Básicamente, esto significa que puede crear una ''Pila'' que consiste en 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 Middleware (s).

#use especifica middleware para usar en una pila. Toma el middleware como argumento.

Rack Middleware debe:

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

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

Así que aquí, debido a lo que Rack::Builder hace bajo el capó, el argumento de app del método de initialize de MessageSetter es MessageApp .

(mueve tu cabeza alrededor de lo anterior antes de continuar)

Por lo tanto, cada pieza de Middleware esencialmente "pasa" el hash de Environment existente a la siguiente aplicación de la cadena, por lo que tiene la oportunidad de mutar ese hash de entorno en el Middleware antes de pasarlo a la siguiente aplicación en 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 a su vez antes de que finalmente sea procesada por la pieza final en la pila (en nuestro caso, MessageApp ). Esto es extremadamente útil porque separa diferentes etapas de procesamiento de solicitudes. En términos de ''separación de preocupaciones'', ¡no podría ser mucho más limpio!

Puede construir un ''pipeline de solicitud'' que consiste en varios Middlewares que tratan con cosas tales como:

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

(por encima de las viñetas de otra respuesta en este hilo)

A menudo verás esto en aplicaciones profesionales de Sinatra. Sinatra utiliza Rack! ¡Vea here para la definición de lo que es Sinatra!

Como nota final, nuestro config.ru se puede escribir en un estilo de mano corta, 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 está haciendo MessageApp , aquí está su versión de "mano larga" que muestra explícitamente que #call está creando una nueva instancia de Rack::Response , con los tres argumentos necesarios.

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

Enlaces útiles

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


Rack 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 canalización para servidores web que usan Rack .

Separa de manera muy clara 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 de la tubería 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 : "¿el usuario está autorizado para realizar esta tarea en particular?", Es decir, la seguridad basada en roles.

  • Almacenamiento en caché : ¿ya procesé esta solicitud? ¿Puedo devolver un resultado 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 respuesta?

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

Ser capaz de separar las diferentes etapas (y, opcionalmente, incluirlas) es una gran ayuda para desarrollar aplicaciones bien estructuradas.

Comunidad

También hay un gran ecosistema que se desarrolla en torno al Rack Middleware: debería poder encontrar componentes de rack preconstruidos para realizar todos los pasos anteriores y mucho más. Consulte 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, pero no está directamente involucrado en la ejecución de alguna tarea. Algunos ejemplos muy comunes son el registro, la autenticación y los otros componentes comunes de procesamiento horizontal . Estas tienden a ser las cosas que todos necesitan en múltiples aplicaciones, pero no muchas personas están interesadas (o deberían estar) en construirse.

Más información


El middleware de rack es una forma de filtrar una solicitud y respuesta que llega a su aplicación. Un componente de middleware se encuentra entre el cliente y el servidor, procesando solicitudes entrantes y respuestas salientes, pero es más que una interfaz que se puede usar para hablar con el servidor web. Se utiliza para agrupar y ordenar módulos, que normalmente son clases de Ruby, y especificar la dependencia entre ellos. El módulo de middleware de rack solo debe: - tener un constructor que tome la siguiente aplicación en la pila como parámetro - responder al método de "llamada", que tome el hash del entorno como un parámetro. El valor de retorno de esta llamada es una matriz de: código de estado, hash del entorno y cuerpo de respuesta.


En primer lugar, Rack es exactamente dos cosas:

  • Una convención de interfaz de servidor web.
  • Una gema

Rack - La interfaz del servidor web

Lo más básico del rack es una simple convención. Cada servidor web compatible con rack siempre invocará un método de llamada en un objeto que le des y le proporcionará el resultado de ese método. Rack especifica exactamente cómo debe verse este método de llamada, y qué tiene que devolver. Eso es rack.

Vamos a darle un intento simple. Usaré WEBrick como servidor web compatible con rack, pero cualquiera de ellos servirá. Vamos a crear una aplicación web simple que devuelve una cadena JSON. Para esto vamos a crear un archivo llamado config.ru. El comando rackup de rack gem llamará automáticamente a config.ru, que simplemente ejecutará el contenido de config.ru en un servidor web compatible con rack. Así que vamos a agregar 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

Como la convención especifica, nuestro servidor tiene un método llamado llamada que acepta un hash de entorno y devuelve una matriz con el formulario [estado, encabezados, cuerpo] para que el servidor web sirva. Vamos a probarlo simplemente llamando rackup. Un servidor predeterminado compatible con el bastidor, tal vez WEBrick o Mongrel se iniciará y esperará inmediatamente a que se atiendan las solicitudes.

$ 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

Probemos nuestro nuevo servidor JSON curling o visitando la url http://localhost:9292/hello.json y voila:

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

Funciona. ¡Genial! Esa es la base para cada framework web, ya sea Rails o Sinatra. En algún punto, implementan un método de llamada, trabajan a través de todo el código del marco y finalmente devuelven una respuesta en la forma típica [estado, encabezados, cuerpo].

En Ruby on Rails, por ejemplo, las solicitudes de rack ActionDispatch::Routing.Mapper clase ActionDispatch::Routing.Mapper que se ve así:

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

Básicamente, los cheques de Rails dependen del env hash si alguna ruta coincide. Si es así, pasa el hash env a la aplicación para calcular la respuesta; de lo contrario, responde de inmediato con un 404. Así, cualquier servidor web que cumpla con la convención de interfaz de rack puede servir una aplicación Rails completamente desarrollada.

Middleware

Rack también admite la creación de capas de middleware. Básicamente, interceptan una solicitud, hacen algo con ella y la pasan. Esto es muy útil para tareas versátiles.

Digamos que queremos agregar el registro a nuestro servidor JSON que también mide el tiempo que tarda una solicitud. Simplemente podemos crear un registrador de middleware que haga 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, se guarda una copia de la aplicación de rack real. En nuestro caso es una instancia de nuestro JSONServer. Rack llama automáticamente al método de llamada en el middleware y espera una matriz de [status, headers, body] , tal como lo hace nuestro JSONServer.

Entonces, en este middleware, se toma el punto de inicio, luego la llamada real al JSONServer se realiza con @app.call(env) , luego el registrador genera la entrada de registro y finalmente devuelve la respuesta como [@status, @headers, @body] .

Para hacer que nuestro pequeño rackup.ru use este middleware, agregue un uso de RackLogger a este:

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

Reinicie el servidor y listo, genera un registro en cada solicitud. Rack le permite agregar múltiples middlewares que se llaman en el orden en que se agregan. Es solo una excelente manera de agregar funcionalidad sin cambiar el núcleo de la aplicación de rack.

Rack - La Gema

Aunque el bastidor, en primer lugar, es una convención, también es una joya que proporciona una gran funcionalidad. Uno de ellos ya lo usamos para nuestro servidor JSON, el comando rackup. ¡Pero hay más! La gema de rack proporciona pequeñas aplicaciones para muchos casos de uso, como servir archivos estáticos o incluso directorios completos. Veamos cómo servimos un archivo simple, 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 deseamos servir este archivo desde la raíz del sitio web, así que agreguemos lo siguiente a nuestro config.ru:

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

Si visitamos http://localhost:9292 , vemos nuestro archivo html perfectamente renderizado. Eso fue fácil, ¿verdad?

Agreguemos un directorio completo de archivos javascript creando algunos archivos javascript en / javascripts y agregando lo siguiente a config.ru:

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

Reinicie el servidor y visite http://localhost:9292/javascript y verá una lista de todos los archivos javascript que puede incluir ahora directamente desde cualquier lugar.




config.ru ejemplo ejecutable 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 visita localhost:9292 . La salida es:

main Middleware

Así que está claro que el Middleware envuelve y llama a la aplicación principal. Por lo tanto, puede preprocesar la solicitud y postprocesar la respuesta de cualquier manera.

Como se explica en: http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack , Rails usa Rack Middlewares para muchas de sus funciones, y también puede agregar su propia propiedad con config.middleware.use Métodos familiares.

La ventaja de implementar la funcionalidad en un middleware es que puede reutilizarlo en cualquier marco de Rack, por lo tanto, todos los principales Ruby, y no solo Rails.