O que é o middleware Rack no Ruby? Não consegui encontrar nenhuma boa explicação para o que eles querem dizer com "middleware".
O que é o middleware Rack no Ruby? Não consegui encontrar nenhuma boa explicação para o que eles querem dizer com "middleware".
Respostas:
O middleware do rack é mais do que "uma maneira de filtrar uma solicitação e resposta" - é uma implementação do padrão de design de pipeline para servidores da Web que usam o Rack .
Ele separa de maneira muito clara as diferentes etapas do processamento de uma solicitação - a separação de preocupações é um objetivo principal de todos os produtos de software bem projetados.
Por exemplo, com o Rack, posso ter estágios separados do pipeline executando:
Autenticação : quando a solicitação chega, os detalhes de logon do usuário estão corretos? Como validar esse OAuth, autenticação básica HTTP, nome / senha?
Autorização : "o usuário está autorizado a executar esta tarefa específica?", Ou seja, segurança baseada em função.
Armazenamento em cache : já processei essa solicitação, posso retornar um resultado em cache?
Decoração : como posso melhorar a solicitação para melhorar o processamento a jusante?
Monitoramento de desempenho e uso : quais estatísticas posso obter da solicitação e resposta?
Execução : efetue o tratamento da solicitação e forneça uma resposta.
Ser capaz de separar os diferentes estágios (e opcionalmente incluí-los) é uma grande ajuda no desenvolvimento de aplicativos bem estruturados.
Há também um ótimo ecossistema em desenvolvimento no Rack Middleware - você deve encontrar componentes de rack pré-criados para executar todas as etapas acima e muito mais. Consulte o wiki do Rack GitHub para obter uma lista do middleware .
Middleware é um termo terrível que se refere a qualquer componente / biblioteca de software que ajude, mas não esteja diretamente envolvido na execução de alguma tarefa. Exemplos muito comuns são log, autenticação e outros componentes de processamento horizontal comuns . Essas tendem a ser as coisas que todos precisam em vários aplicativos, mas muitas pessoas não estão interessadas (ou deveriam estar) em se desenvolver.
O comentário sobre como ser uma maneira de filtrar solicitações provavelmente vem do elenco da tela do episódio 151 do RailsCast: Rack Middleware .
O middleware do rack foi desenvolvido a partir do rack e há uma excelente introdução ao Introdução ao middleware do rack .
Há uma introdução ao middleware na Wikipedia aqui .
Primeiro de tudo, o Rack é exatamente duas coisas:
Rack - a interface do servidor da Web
O básico do rack é uma convenção simples. Todo servidor da web compatível com rack sempre chama um método de chamada em um objeto que você fornece a ele e serve o resultado desse método. O rack especifica exatamente como esse método de chamada deve ser e o que ele deve retornar. Isso é tortura.
Vamos fazer uma tentativa simples. Usarei o WEBrick como servidor da web compatível com rack, mas qualquer um deles o fará. Vamos criar um aplicativo Web simples que retorne uma sequência JSON. Para isso, criaremos um arquivo chamado config.ru. O config.ru será chamado automaticamente pelo conjunto de comandos da gem do rack, que simplesmente executará o conteúdo do config.ru em um servidor da web compatível com rack. Então, vamos adicionar o seguinte ao arquivo 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 a convenção especifica, nosso servidor possui um método chamado call que aceita um hash do ambiente e retorna uma matriz com o formato [status, headers, body] para o servidor da web servir. Vamos testá-lo simplesmente chamando de rackup. Um servidor compatível com rack padrão, talvez o WEBrick ou Mongrel seja iniciado e aguarde imediatamente a entrega das solicitaçõ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
Vamos testar nosso novo servidor JSON, curvando ou visitando o URL http://localhost:9292/hello.json
e voila:
$ curl http://localhost:9292/hello.json
{ message: "Hello!" }
Funciona. Ótimo! Essa é a base para todo framework web, seja Rails ou Sinatra. Em algum momento, eles implementam um método de chamada, trabalham com todo o código da estrutura e, finalmente, retornam uma resposta na forma típica [status, headers, body].
No Ruby on Rails, por exemplo, as solicitações de rack atingem a ActionDispatch::Routing.Mapper
classe que se parece com isso:
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
Então, basicamente, o Rails verifica, dependendo do hash env, se alguma rota corresponder. Nesse caso, ele passa o hash env para o aplicativo para calcular a resposta, caso contrário, ele responde imediatamente com um 404. Portanto, qualquer servidor da Web que seja compatível com a convenção de interface do rack poderá atender a um aplicativo Rails totalmente desenvolvido.
Middleware
O rack também suporta a criação de camadas de middleware. Eles basicamente interceptam uma solicitação, fazem algo com ela e a transmitem. Isso é muito útil para tarefas versáteis.
Digamos que queremos adicionar log ao nosso servidor JSON que também mede quanto tempo uma solicitação leva. Podemos simplesmente criar um registrador de middleware que faça exatamente isso:
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
Quando é criado, ele salva uma cópia do aplicativo real do rack. No nosso caso, é uma instância do nosso JSONServer. O rack chama automaticamente o método de chamada no middleware e espera retornar uma [status, headers, body]
matriz, assim como nosso JSONServer retorna.
Portanto, nesse middleware, o ponto de partida é adotado, a chamada real para o JSONServer é feita @app.call(env)
e o logger gera a entrada de log e, finalmente, retorna a resposta como [@status, @headers, @body]
.
Para fazer nosso pequeno rackup.ru usar esse middleware, adicione um RackLogger de uso como 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 o servidor e pronto, ele gera um log em cada solicitação. O rack permite adicionar vários middlewares chamados na ordem em que são adicionados. É apenas uma ótima maneira de adicionar funcionalidade sem alterar o núcleo do aplicativo em rack.
Rack - A jóia
Embora o rack - antes de tudo - seja uma convenção, ele também é uma jóia que fornece ótima funcionalidade. Um deles já usamos para o nosso servidor JSON, o comando rackup. Mas tem mais! A jóia do rack fornece pequenos aplicativos para muitos casos de uso, como servir arquivos estáticos ou mesmo diretórios inteiros. Vamos ver como servimos um arquivo simples, por exemplo, um arquivo HTML muito básico localizado em htmls / index.html:
<!DOCTYPE HTML>
<html>
<head>
<title>The Index</title>
</head>
<body>
<p>Index Page</p>
</body>
</html>
Talvez desejemos servir esse arquivo a partir da raiz do site, então vamos adicionar o seguinte ao nosso config.ru:
map '/' do
run Rack::File.new "htmls/index.html"
end
Se o visitarmos http://localhost:9292
, veremos nosso arquivo html perfeitamente renderizado. Isso foi fácil, certo?
Vamos adicionar um diretório inteiro de arquivos javascript, criando alguns arquivos javascript em / javascripts e adicionando o seguinte ao config.ru:
map '/javascripts' do
run Rack::Directory.new "javascripts"
end
Reinicie o servidor e visite http://localhost:9292/javascript
e você verá uma lista de todos os arquivos javascript que você pode incluir agora diretamente de qualquer lugar.
Eu tive um problema para entender o Rack por um bom tempo. Eu só o entendi completamente depois de trabalhar na criação desse servidor Web em miniatura Ruby . Compartilhei meus aprendizados sobre o Rack (na forma de uma história) aqui no meu blog: http://gauravchande.com/what-is-rack-in-ruby-rails
O feedback é mais que bem-vindo.
config.ru
exemplo executável 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)
Corra rackup
e visite localhost:9292
. A saída é:
main
Middleware
Portanto, é claro que o Middleware
embrulho e chama o aplicativo principal. Portanto, ele pode pré-processar a solicitação e pós-processar a resposta de qualquer maneira.
Conforme explicado em: http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack , o Rails usa middlewares de rack para muitas funcionalidades, e você também pode adicionar config.middleware.use
os métodos da família.
A vantagem de implementar a funcionalidade em um middleware é que você pode reutilizá-la em qualquer estrutura Rack, portanto, todas as principais Ruby, e não apenas Rails.
O middleware em rack é uma maneira de filtrar uma solicitação e resposta que entra no seu aplicativo. Um componente de middleware fica entre o cliente e o servidor, processando solicitações de entrada e respostas de saída, mas é mais do que uma interface que pode ser usada para conversar com o servidor da web. É usado para agrupar e solicitar módulos, que geralmente são classes Ruby, e especificar a dependência entre elas. O módulo de middleware do rack deve apenas: - ter o construtor que leva o próximo aplicativo na pilha como parâmetro - responder ao método "call", que usa o hash do ambiente como parâmetro. O retorno do valor desta chamada é uma matriz de: código de status, hash do ambiente e corpo da resposta.
Eu usei o middleware Rack para resolver alguns problemas:
Forneceu correções bastante elegantes em ambos os casos.
O rack fornece uma interface mínima entre os servidores da web que suportam as estruturas Ruby e Ruby.
Usando o Rack, você pode escrever um Aplicativo de Rack.
O rack transmitirá o hash do ambiente (um Hash, contido em uma solicitação HTTP de um cliente, que consiste em cabeçalhos semelhantes a CGI) para o aplicativo de rack, que pode usar as coisas contidas nesse hash para fazer o que quiser.
Para usar o Rack, você deve fornecer um 'aplicativo' - um objeto que responda ao #call
método com o Hash do ambiente como parâmetro (normalmente definido como env
). #call
deve retornar uma matriz de exatamente três valores:
each
).Você pode escrever um aplicativo de rack que retorne essa matriz - isso será enviado de volta ao seu cliente, pelo rack, dentro de uma resposta (na verdade, essa é uma instância da classe Rack::Response
[clique para acessar a documentação]).
gem install rack
config.ru
arquivo - o Rack sabe procurar por isso.Criaremos um pequeno aplicativo de rack que retornará uma resposta (uma instância de Rack::Response
) cujo corpo de resposta seja uma matriz que contenha uma String:"Hello, World!"
.
Iniciaremos um servidor local usando o comando rackup
.
Ao visitar a porta relevante em nosso navegador, veremos "Olá, mundo!" renderizado na janela de exibição.
#./message_app.rb
class MessageApp
def call(env)
[200, {}, ['Hello, World!']]
end
end
#./config.ru
require_relative './message_app'
run MessageApp.new
Inicie um servidor local rackup
e visite localhost: 9292 e você verá 'Olá, mundo!' prestados.
Essa não é uma explicação abrangente, mas essencialmente o que acontece aqui é que o Cliente (o navegador) envia uma Solicitação HTTP ao Rack, através do servidor local, e o Rack instancia MessageApp
e executa call
, passando o Environment Hash como parâmetro para o método ( o env
argumento).
O rack pega o valor de retorno (a matriz) e o usa para criar uma instância Rack::Response
e envia de volta ao cliente. O navegador usa magia para imprimir 'Hello, World!' para a tela.
Aliás, se você quiser ver como é o hash do ambiente, basta colocar puts env
embaixodef call(env)
.
Por menor que seja, o que você escreveu aqui é um aplicativo Rack!
Em nosso pequeno aplicativo Rack, podemos interagir com o env
hash (veja aqui mais sobre o hash do ambiente).
Implementaremos a capacidade do usuário inserir sua própria string de consulta na URL; portanto, essa string estará presente na solicitação HTTP, encapsulada como um valor em um dos pares de chave / valor do hash do ambiente.
Nosso aplicativo Rack acessará essa sequência de consultas no hash Environment e a enviará de volta ao cliente (nosso navegador, neste caso) por meio do Corpo na resposta.
Dos documentos do rack sobre a Hash do ambiente: "QUERY_STRING: a parte do URL da solicitação que segue o?, Se houver. Pode estar vazia, mas é sempre necessária!"
#./message_app.rb
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
Agora, rackup
visite localhost:9292?hello
( ?hello
sendo a string de consulta) e você verá 'olá' processado na janela de exibição.
Nós vamos:
MessageSetter
,env
,MessageSetter
irá inserir uma 'MESSAGE'
chave no hash env, seu valor sendo 'Hello, World!'
se env['QUERY_STRING']
estiver vazio;env['QUERY_STRING']
se não,@app.call(env)
- @app
ser o próximo aplicativo na 'pilha': MessageApp
.Primeiro, a versão 'mão longa':
#./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
Nos documentos do Rack :: Builder , vemos queRack::Builder
implementa um pequeno DSL para construir iterativamente aplicativos de rack. Isso basicamente significa que você pode criar uma 'Pilha' consistindo em um ou mais Middlewares e um aplicativo 'nível inferior' para o qual enviar. Todas as solicitações enviadas ao seu aplicativo de nível inferior serão processadas primeiro pelo (s) seu (s) Middleware (s).
#use
especifica o middleware a ser usado em uma pilha. Leva o middleware como argumento.
O Middleware do rack deve:
call
método que usa o hash do ambiente como parâmetro.No nosso caso, o 'Middleware' é MessageSetter
, o 'construtor' é o initialize
método do MessageSetter , o 'próximo aplicativo' na pilha éMessageApp
.
Então, aqui, por causa do que Rack::Builder
faz sob o capô, o app
argumento de MessageSetter
's initialize
método é MessageApp
.
(contorne o item acima antes de prosseguir)
Portanto, cada parte do Middleware basicamente 'transmite' o hash de ambiente existente para o próximo aplicativo na cadeia - para que você tenha a oportunidade de alterar esse hash de ambiente no Middleware antes de passá-lo para o próximo aplicativo na pilha.
#run
aceita um argumento que é um objeto que responde #call
e retorna uma resposta em rack (uma instância de Rack::Response
).
Usando Rack::Builder
você pode construir cadeias de Middlewares e qualquer solicitação para seu aplicativo será processada por cada Middleware, por sua vez, antes de finalmente ser processada pela parte final da pilha (no nosso caso MessageApp
). Isso é extremamente útil porque separa diferentes estágios dos pedidos de processamento. Em termos de 'separação de preocupações', não poderia ser muito mais limpo!
Você pode construir um 'pipeline de solicitação' que consiste em vários Middlewares que lidam com coisas como:
(pontos de marcador acima de outra resposta neste tópico)
Você verá isso frequentemente em aplicativos profissionais do Sinatra. Sinatra usa Rack! Veja aqui a definição do que Sinatra é!
Como observação final, config.ru
podemos escrever em estilo abreviado, produzindo exatamente a mesma funcionalidade (e é isso que você normalmente verá):
require_relative './message_app'
require_relative './middleware/message_setter'
use MessageSetter
run MessageApp.new
E para mostrar mais explicitamente o que MessageApp
está fazendo, aqui está sua versão de 'mão longa' que mostra explicitamente que #call
está criando uma nova instância de Rack::Response
, com os três argumentos necessários.
class MessageApp
def call(env)
Rack::Response.new([env['MESSAGE']], 200, {})
end
end
Rack - a interface entre o servidor de aplicativos e a Web
Rack é um pacote Ruby que fornece uma interface para um servidor web se comunicar com o aplicativo. É fácil adicionar componentes de middleware entre o servidor da Web e o aplicativo para modificar a maneira como sua solicitação / resposta se comporta. O componente de middleware fica entre o cliente e o servidor, processando solicitações de entrada e respostas de saída.
Em palavras leigas, é basicamente apenas um conjunto de diretrizes sobre como um servidor e um aplicativo Rails (ou qualquer outro aplicativo da web Ruby) devem conversar entre si .
Para usar o Rack, forneça um "aplicativo": um objeto que responda ao método de chamada, usando o hash do ambiente como parâmetro e retornando uma matriz com três elementos:
Para mais explicações, você pode seguir os links abaixo.
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
No rails, temos o config.ru como um arquivo em rack, você pode executar qualquer arquivo em rack com o rackup
comando E a porta padrão para isso é 9292
. Para testar isso, você pode simplesmente rodar rackup
no diretório rails e ver o resultado. Você também pode atribuir a porta na qual deseja executá-la. O comando para executar o arquivo em rack em qualquer porta específica é
rackup -p PORT_NUMBER
Rack é uma jóia que fornece uma interface simples para abstrair a solicitação / resposta HTTP. O rack fica entre estruturas da web (Rails, Sinatra etc.) e servidores da web (unicórnio, puma) como um adaptador. Da imagem acima, isso mantém o servidor unicórnio completamente independente de saber sobre trilhos e os trilhos não sabem sobre unicórnio. Este é um bom exemplo de acoplamento solto , separação de preocupações .
A imagem acima é desta palestra da conferência sobre trilhos no rack https://youtu.be/3PnUV9QzB0g Eu recomendo assistir para um entendimento mais profundo.