¿Qué es el middleware Rack en Ruby? No pude encontrar ninguna buena explicación de lo que quieren decir con "middleware".
¿Qué es el middleware Rack en Ruby? No pude encontrar ninguna buena explicación de lo que quieren decir con "middleware".
Respuestas:
El middleware de Rack es más que "una forma de filtrar una solicitud y respuesta": es una implementación del patrón de diseño de canalización para servidores web que usan Rack .
Separa de forma muy clara las diferentes etapas de procesamiento de una solicitud: la separación de las 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 : "¿está autorizado el usuario para realizar esta tarea en particular?", Es decir, seguridad basada en roles.
Almacenamiento en caché : ya he procesado 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 la respuesta?
Ejecución : realmente maneja la solicitud y proporciona una respuesta.
Ser capaz de separar las diferentes etapas (y opcionalmente incluirlas) es de gran ayuda para desarrollar aplicaciones bien estructuradas.
También se está desarrollando un gran ecosistema en torno a Rack Middleware: debería poder encontrar componentes de rack preconstruidos para realizar todos los pasos anteriores y más. Consulte el wiki de Rack GitHub para obtener una lista de middleware .
Middleware es un término terrible que se refiere a cualquier componente / biblioteca de software que ayuda pero no está directamente involucrado en la ejecución de alguna tarea. Ejemplos muy comunes son el registro, la autenticación y otros componentes comunes de procesamiento horizontal . Estos tienden a ser lo que todo el mundo necesita en múltiples aplicaciones, pero no muchas personas están interesadas (o deberían estar) en construirse.
El comentario sobre que es una forma de filtrar solicitudes probablemente proviene del episodio 151 de RailsCast: reparto de pantalla de Rack Middleware .
El middleware Rack surgió de Rack y hay una gran introducción en Introducción al middleware Rack .
Hay una introducción al middleware en Wikipedia aquí .
En primer lugar, Rack es exactamente dos cosas:
Rack: la interfaz del servidor web
Los conceptos básicos de rack son una convención simple. Cada servidor web compatible con bastidores siempre llamará a un método de llamada en un objeto que le proporcione y servirá 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 estante.
Vamos a intentarlo de manera simple. Usaré WEBrick como servidor web compatible con rack, pero cualquiera de ellos funcionará. Creemos una aplicación web simple que devuelva una cadena JSON. Para esto crearemos un archivo llamado config.ru. Config.ru será llamado automáticamente por el comando rackup de la gema del rack, que simplemente ejecutará el contenido de config.ru en un servidor web compatible con el rack. Así que agreguemos 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 call que acepta un hash de entorno y devuelve una matriz con la forma [estado, encabezados, cuerpo] para que el servidor web sirva. Probémoslo simplemente llamando al acumulador. Un servidor compatible con rack predeterminado, tal vez WEBrick o Mongrel se iniciarán y esperarán inmediatamente a que se envíen 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 curvando o visitando la url http://localhost:9292/hello.json
y listo:
$ curl http://localhost:9292/hello.json
{ message: "Hello!" }
Funciona. ¡Excelente! Esa es la base de cada marco web, ya sea Rails o Sinatra. En algún momento implementan un método de llamada, trabajan a través de todo el código marco y finalmente devuelven una respuesta en la forma típica [estado, encabezados, cuerpo].
En Ruby on Rails, por ejemplo, las solicitudes de rack llegan a la ActionDispatch::Routing.Mapper
clase 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, las comprobaciones de Rails dependen del hash env si alguna ruta coincide. Si es así, pasa el hash env a la aplicación para calcular la respuesta, de lo contrario, responde inmediatamente con un 404. Por lo tanto, cualquier servidor web que cumpla con la convención de interfaz de rack puede servir una aplicación Rails totalmente desarrollada.
Middleware
Rack también admite la creación de capas de middleware. Básicamente interceptan una solicitud, hacen algo con ella y la transmiten. Esto es muy útil para tareas versátiles.
Supongamos que queremos agregar el registro a nuestro servidor JSON que también mide cuánto tiempo 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, esa es una instancia de nuestro JSONServer. Rack llama automáticamente al método de llamada en el middleware y espera una [status, headers, body]
matriz, al igual que nuestro JSONServer regresa.
Entonces, en este middleware, se toma el punto de inicio, luego se realiza la llamada real al JSONServer @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 RackLogger así:
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 simplemente una excelente manera de agregar funcionalidad sin cambiar el núcleo de la aplicación de rack.
Estante - La Gema
Aunque el rack, 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>
Quizás queramos 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.
Tuve un problema para entender a Rack por una buena cantidad de tiempo. Solo lo entendí completamente después de trabajar en hacer este servidor web Ruby en miniatura . He compartido mis conocimientos sobre Rack (en forma de historia) aquí en mi blog: http://gauravchande.com/what-is-rack-in-ruby-rails
La retroalimentación es más que bienvenida.
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)
Corre rackup
y visita localhost:9292
. El resultado es:
main
Middleware
Por lo tanto, está claro que se Middleware
envuelve y llama a la aplicación principal. Por lo tanto, puede preprocesar la solicitud y procesar la respuesta de cualquier manera.
Como se explica en: http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack , Rails utiliza los middlewares de Rack para muchas de sus funciones, y también puede agregar el suyo propio 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, en todos los principales Ruby, y no solo en Rails.
El middleware en rack es una forma de filtrar una solicitud y una respuesta que ingresan 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 generalmente son clases de Ruby, y especificar la dependencia entre ellos. El módulo de middleware de bastidor 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 parámetro. El valor de retorno de esta llamada es una matriz de: código de estado, hash de entorno y cuerpo de respuesta.
He usado el middleware Rack para resolver un par de problemas:
Proporcionó soluciones bastante elegantes en ambos casos.
Rack proporciona una interfaz mínima entre servidores web que admiten los marcos Ruby y Ruby.
Usando Rack puedes escribir una Aplicación Rack.
Rack pasará el hash de entorno (un hash, contenido dentro de una solicitud HTTP de un cliente, que consiste en encabezados similares a CGI) a su aplicación Rack, que puede usar las cosas contenidas en este hash para hacer lo que quiera.
Para usar Rack, debe proporcionar una 'aplicación', un objeto que responde al #call
método con el Hash de entorno como parámetro (normalmente definido como env
). #call
debe devolver una matriz de exactamente tres valores:
each
).Puede escribir una aplicación Rack que devuelva dicha matriz; Rack la enviará de vuelta a su cliente dentro de un Respuesta (en realidad, será una instancia de la Clase Rack::Response
[haga clic para ir a los documentos]).
gem install rack
config.ru
archivo: Rack sabe buscar esto.Vamos a crear una pequeña aplicación Rack que devuelve una respuesta (una instancia de Rack::Response
) que es la respuesta del cuerpo es una matriz que contiene una cadena de caracteres: "Hello, World!"
.
Lanzaremos un servidor local usando el comando rackup
.
Al visitar 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
Inicie un servidor local con rackup
y visite localhost: 9292 y debería ver '¡Hola, mundo!' prestados.
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 instancias MessageApp
y ejecutacall
, pasando el Hash del entorno como un parámetro en el método ( el env
argumento)
Rack toma el valor de retorno (la matriz) y lo usa para crear una instancia Rack::Response
y lo envía de vuelta al Cliente. El navegador usa 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
debajodef call(env)
.
Minimal como es, ¡lo que has escrito aquí es una aplicación Rack!
En nuestra pequeña aplicación Rack, podemos interactuar con el env
hash (ver aquí para obtener más información sobre el hash del entorno).
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 Environment 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 Environment Hash: "QUERY_STRING: la parte de la URL de solicitud que sigue a?, Si la hay. Puede estar vacía, ¡pero siempre es necesaria!"
#./message_app.rb
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
Ahora, rackup
visite localhost:9292?hello
( ?hello
siendo la cadena de consulta) y debería ver 'hola' en la ventana gráfica.
Lo haremos:
MessageSetter
,env
,MessageSetter
insertará una 'MESSAGE'
clave en el hash env, siendo su valor 'Hello, World!'
si env['QUERY_STRING']
está vacío; env['QUERY_STRING']
si no,@app.call(env)
- @app
siendo la próxima aplicación de la 'pila': MessageApp
.Primero, la versión de '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
Desde los documentos de Rack :: Builder vemos queRack::Builder
implementa un pequeño DSL para construir iterativamente aplicaciones de Rack. Básicamente, esto significa que puede construir una 'Pila' que consta de uno o más Middlewares y una aplicación de 'nivel inferior' para enviar. Todas las solicitudes que pasen a su aplicación de nivel inferior serán procesadas primero por su (s) Middleware (s).
#use
especifica middleware para usar en una pila. Toma el middleware como argumento.
El Middleware de Rack debe:
call
método que toma el hash de entorno como parámetro.En nuestro caso, el 'Middleware' es MessageSetter
, el 'constructor' es el initialize
método de MessageSetter , la 'próxima aplicación' en la pila esMessageApp
.
Así que aquí, debido a lo que Rack::Builder
lo hace bajo el capó, el app
argumento de MessageSetter
's initialize
método es MessageApp
.
(pon tu cabeza alrededor de lo anterior antes de continuar)
Por lo tanto, cada pieza de Middleware esencialmente 'pasa' el hash del entorno existente a la siguiente aplicación en 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 en la pila.
#run
toma un argumento que es un objeto que responde #call
y devuelve una Respuesta de rack (una instancia de Rack::Response
).
Utilizando 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 las diferentes etapas de procesamiento de solicitudes. En términos de 'separación de preocupaciones', ¡no podría ser mucho más limpio!
Puede construir una 'tubería de solicitud' que consta de varios Middlewares que se ocupan de cosas como:
(puntos anteriores de otra respuesta en este hilo)
A menudo verá esto en aplicaciones profesionales de Sinatra. ¡Sinatra usa Rack! Ver aquí para la definición de lo que Sinatra ES !
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 MessageApp
está haciendo, 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 requeridos.
class MessageApp
def call(env)
Rack::Response.new([env['MESSAGE']], 200, {})
end
end
Rack: la interfaz en blanco y negro del servidor web y de aplicaciones
Rack es un paquete de 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 el comportamiento de su solicitud / respuesta. El componente de middleware se encuentra entre el cliente y el servidor, procesando solicitudes entrantes y respuestas salientes.
En palabras simples, es básicamente 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 usar Rack, proporcione una "aplicación": un objeto que responda al método de llamada, tomando el hash del entorno como parámetro y devolviendo una matriz con tres elementos:
Para más explicaciones, puede seguir los siguientes enlaces.
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 de rack, puede ejecutar cualquier archivo de rack con el rackup
comando. Y el puerto predeterminado para esto es 9292
. Para probar esto, simplemente puede ejecutar rackup
en su directorio de rieles y ver el resultado. También puede asignar el puerto en el que desea ejecutarlo. El comando para ejecutar el archivo en rack en cualquier puerto específico es
rackup -p PORT_NUMBER
Rack es una gema que proporciona una interfaz simple para abstraer la solicitud / respuesta HTTP. Rack se encuentra entre los frameworks web (Rails, Sinatra, etc.) y los servidores web (unicornio, puma) como un adaptador. De la imagen de arriba, esto mantiene al servidor de unicornio completamente independiente de saber sobre rieles y rieles no sabe sobre unicornio. Este es un buen ejemplo de acoplamiento flojo , separación de preocupaciones .
La imagen es de esta charla conferencia de carriles sobre una rejilla https://youtu.be/3PnUV9QzB0g Recomiendo ver que para una comprensión más profunda.