O que é o middleware do rack?
-
20-09-2019 - |
Pergunta
O que é o middleware do rack em Ruby? Não consegui encontrar nenhuma boa explicação para o que eles querem dizer com "middleware".
Solução
Rack como design
O middleware do rack é mais do que "uma maneira de filtrar uma solicitação e resposta" - é uma implementação do Padrão de design do pipeline Para servidores da web usando Prateleira.
Ele separa de maneira limpa, os diferentes estágios do processamento de uma solicitação - a separação de preocupações é uma meta -chave de todos os produtos de software bem projetados.
Por exemplo, com rack, posso ter estágios separados do pipeline:
Autenticação: Quando a solicitação chega, os detalhes do logon dos usuários estão corretos? Como posso validar este OAuth, HTTP Basic Authentication, Name/Senha?
Autorização: "O usuário está autorizado a executar essa tarefa em particular?", ou seja, segurança baseada em funções.
Cache: Já processei essa solicitação, posso devolver um resultado em cache?
Decoração: Como posso aprimorar a solicitação para melhorar o processamento a jusante?
Monitoramento de desempenho e uso: Que estatísticas posso obter da solicitação e resposta?
Execução: Na verdade, lide com a 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.
Comunidade
Há também um ótimo sistema ecológico em torno do middleware do rack-você deve encontrar componentes de rack pré-criados para fazer todas as etapas acima e muito mais. Ver O rack github wiki para uma lista de middleware.
O que é middleware?
O Middleware é um termo terrível que se refere a qualquer componente/biblioteca de software que auxilie, mas não está diretamente envolvido na execução de alguma tarefa. Exemplos muito comuns são log, autenticação e outro Componentes comuns de processamento horizontal. Essas tendem a ser as coisas que todos precisam em várias aplicações, mas não muitas pessoas estão interessadas (ou devem estar) em se construir.
Mais Informações
O comentário sobre ser uma maneira de filtrar solicitações provavelmente vem do Episódio 151 do Railscast: Middleware do rack Screen Cast.
O middleware do rack evoluiu para fora do rack e há uma ótima introdução em Introdução ao middleware do rack.
Há uma introdução ao middleware na Wikipedia aqui.
Outras dicas
Primeiro de tudo, Rack são exatamente duas coisas:
- Uma convenção de interface da web servidor
- Uma jóia
Rack - a interface do servidor da web
O básico do rack é uma convenção simples. Cada servidor da web compatível com rack sempre ligará para um método de chamada em um objeto que você dá e servirá ao resultado desse método. O rack especifica exatamente como esse método de chamada deve ser e como ele deve retornar. Isso é rack.
Vamos tentar simples. Vou usar o Webrick como servidor da web compatível com o rack, mas qualquer um deles fará. Vamos criar um aplicativo Web simples que retorne uma string json. Para isso, criaremos um arquivo chamado config.ru. O config.ru será chamado automaticamente pelo rackup de comando do rack gem, que simplesmente executará o conteúdo do config.ru em um servidor da web compatível com o 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 que aceita um hash de ambiente e retorna uma matriz com o formulário [status, cabeçalhos, corpo] para o servidor da web servir. Vamos experimentar simplesmente chamando o rackup. Um servidor compatível com o rack padrão, talvez Webrick ou Mongrel inicie e aguardem imediatamente solicitações para atender.
$ 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, enrolando ou visitando o URL http://localhost:9292/hello.json
e pronto:
$ curl http://localhost:9292/hello.json
{ message: "Hello!" }
Funciona. Excelente! Essa é a base para todas as estruturas da web, sejam trilhos ou Sinatra. Em algum momento, eles implementam um método de chamada, trabalham em todo o código da estrutura e finalmente retornam uma resposta na forma típica [status, cabeçalhos, corpo].
Em Ruby on Rails, por exemplo, as solicitações de rack atingem o ActionDispatch::Routing.Mapper
classe que se parece com a seguinte:
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, os trilhos de trilhos, dependentes do hash Env, se alguma rota corresponder. Nesse caso, ele passa o Hash do Env para o aplicativo para calcular a resposta, caso contrário, ele responde imediatamente com um 404. Portanto, qualquer servidor da web que esteja em conformidade com a Convenção da Interface do Rack, é capaz de atender a um aplicativo Rails totalmente soprado.
Middleware
O rack também suporta a criação de camadas de middleware. Eles basicamente interceptam uma solicitação, fazem algo com ele e transmitam -o. Isso é muito útil para tarefas versáteis.
Digamos que queremos adicionar logs ao nosso servidor JSON que também mede quanto tempo leva uma solicitação. Podemos simplesmente criar um middleware madeireiro 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, salva uma cópia do aplicativo real do rack. No nosso caso, esse é um exemplo do nosso JSonserver. O rack chama automaticamente o método de chamada no middleware e espera de volta um [status, headers, body]
Array, assim como o nosso JSonserver retorna.
Então, neste middleware, o ponto de partida é levado, então a chamada real para o jsonserver é feita com @app.call(env)
, então o madeireiro gera a entrada de log e finalmente retorna a resposta como [@status, @headers, @body]
.
Para fazer nosso pequeno rackup.ru usar este middleware, adicione um RackLogger de uso assim:
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 o voila, ele gera um log em todas as solicitações. O rack permite que você adicione vários usuários médios que são chamados na ordem em que são adicionados. É apenas uma ótima maneira de adicionar funcionalidade sem alterar o núcleo do aplicativo rack.
Rack - a jóia
Embora o rack - antes de tudo - seja uma convenção, também é uma jóia que fornece uma grande funcionalidade. Um deles já usamos para o nosso servidor JSON, o comando rackup. Mas há mais! O rack gem fornece pequenas aplicações para muitos casos de uso, como servir arquivos estáticos ou até 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 queira servir este arquivo 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 visitarmos http://localhost:9292
Vemos 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 em entender o Rack Me por muito tempo. Eu só entendi completamente depois de trabalhar para fazer isso servidor da web rubi em miniatura Eu mesmo. Compartilhei meus aprendizados sobre o rack (na forma de uma história) aqui no meu blog: http://gauravchande.com/what-is-sack-in-by-rails
O feedback é mais do que bem -vindo.
config.ru
Exemplo mínimo de execução
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
e visite localhost:9292
. A saída é:
main
Middleware
Então é claro que o Middleware
envolve e chama o aplicativo principal. Portanto, é capaz de pré-processar a solicitação e pós-processo da resposta de qualquer forma.
Conforme explicado em: http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack , O Rails usa o rack médio para muitas de sua funcionalidade, e você pode adicionar você também com config.middleware.use
Métodos familiares.
A vantagem de implementar a funcionalidade em um middleware é que você pode reutilizá -la em qualquer estrutura de rack, portanto, todos os principais rubi, e não apenas trilhos.
O middleware do rack é uma maneira de filtrar uma solicitação e resposta entrando 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 interface que pode ser usada para conversar com o servidor da web. É usado para agrupar e pedir módulos, que geralmente são classes de rubi, e especificam dependência entre eles. O módulo de middleware do rack deve apenas: - ter construtor que leva a próxima aplicação na pilha como parâmetro - responda ao método "chamado", que toma o hash do ambiente como um parâmetro. O valor de retorno desta chamada é uma matriz de: código de status, hash de ambiente e órgão de resposta.
Eu usei o middleware do rack para resolver alguns problemas:
- Capturando erros de análise JSON com middleware de rack personalizado e retornar mensagens de erro bem formatadas quando o cliente envia JSON BUSTED
- Compressão de conteúdo via rack :: deflater
Proporcionou correções bastante elegantes em ambos os casos.
O que é rack?
O rack fornece uma interface mínima entre os servidores da web que suportam estruturas Ruby e Ruby.
Usando o rack, você pode escrever um aplicativo de rack.
O rack passará no hash do ambiente (um hash, contido dentro de uma solicitação HTTP de um cliente, composto por cabeçalhos do tipo CGI) no aplicativo de rack que pode usar as coisas contidas neste hash para fazer o que quiser.
O que é um aplicativo de rack?
Para usar o rack, você deve fornecer um 'aplicativo' - um objeto que responde ao #call
método com o hash do ambiente como um parâmetro (normalmente definido como env
). #call
Deve retornar uma matriz de exatamente três valores:
- a Código de status (por exemplo, '200'),
- uma Hash de cabeçalhos,
- a Corpo de resposta (que deve responder ao método Ruby,
each
).
Você pode escrever um aplicativo de rack que retorne essa matriz - isso será enviado de volta ao seu cliente, por rack, dentro de um Resposta (Isso será realmente um instância da classe Rack::Response
Clique para ir para os documentos]).
Um aplicativo de rack muito simples:
gem install rack
- Crie um
config.ru
Arquivo - Rack sabe procurar isso.
Vamos criar um pequeno aplicativo de rack que retorne uma resposta (uma instância de Rack::Response
) O corpo de resposta de quem é uma matriz que contém uma string: "Hello, World!"
.
Vamos iniciar um servidor local usando o comando rackup
.
Ao visitar a porta relevante em nosso navegador, veremos "Olá, mundo!" renderizado na visualização.
#./message_app.rb
class MessageApp
def call(env)
[200, {}, ['Hello, World!']]
end
end
#./config.ru
require_relative './message_app'
run MessageApp.new
Iniciar um servidor local com rackup
e visite Localhost: 9292 E você deve ver 'Olá, mundo!' renderizado.
Esta não é uma explicação abrangente, mas essencialmente o que acontece aqui é que o cliente (o navegador) envia uma solicitação HTTP para o rack, através do servidor local, e instanciados por rack MessageApp
e corre call
, passando no ambiente hash como um parâmetro para o método (o env
argumento).
Rack assume o valor de retorno (a matriz) e o usa para criar uma instância de Rack::Response
e envia isso de volta ao cliente. O navegador usa Magia Para imprimir 'Olá, mundo!' para a tela.
Aliás, se você quiser ver como é o hash do ambiente, basta colocar puts env
por baixo def call(env)
.
Mínimo por que seja, o que você escreveu aqui é um aplicativo de rack!
Fazer um aplicativo de rack interagir com o hash de ambiente de entrada
Em nosso pequeno aplicativo de rack, podemos interagir com o env
Hash (veja aqui Para mais informações sobre o hash do ambiente).
Implementaremos a capacidade de o usuário inserir sua própria sequência de consulta no 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 do Hash do ambiente e o enviará de volta ao cliente (nosso navegador, neste caso) através do corpo na resposta.
Dos documentos do rack no ambiente Hash:"Query_string: a parte do URL de solicitação que segue o?, Se houver. Pode estar vazio, mas sempre é necessário!"
#./message_app.rb
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
Agora, rackup
e visite localhost:9292?hello
(?hello
sendo a string de consulta) e você deve ver 'olá' renderizado na viewport.
Middleware do rack
Nós vamos:
- Insira um pedaço de middleware em nossa base de código - uma classe:
MessageSetter
, - O hash do ambiente atingirá esta classe primeiro e será passado como um parâmetro:
env
, MessageSetter
irá inserir um'MESSAGE'
Chave para o hash Env, seu valor sendo'Hello, World!'
E seenv['QUERY_STRING']
está vazia;env['QUERY_STRING']
se não,- Finalmente, vai retornar
@app.call(env)
-@app
sendo o próximo aplicativo na 'pilha':MessageApp
.
Primeiro, a versã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
De Rack :: Builder Docs nós vemos que Rack::Builder
implementa um pequeno DSL para construir iterativamente os aplicativos de rack. Isso basicamente significa que você pode construir uma 'pilha' composta por um ou mais utensílios médios e um aplicativo de 'nível inferior' para despachar. Todas as solicitações que vão para o seu aplicativo de nível inferior serão processadas pela primeira vez pelo (s) seu (s) middleware (s).
#use
Especifica o middleware para usar em uma pilha. É preciso o middleware como um argumento.
O middleware do rack deve:
- Tenha um construtor que pegue o próximo aplicativo na pilha como um parâmetro.
- responda ao
call
Método que toma o hash do ambiente como um parâmetro.
No nosso caso, o 'middleware' é MessageSetter
, o 'construtor' é o de mensagens initialize
método, 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
.
(Coloque a cabeça acima antes de seguir em frente)
Portanto, cada peça de middleware essencialmente 'passa' o hash do ambiente existente para o próximo aplicativo na cadeia - para que você tenha a oportunidade de mudar esse hash de ambiente dentro do middleware antes de passá -lo para o próximo aplicativo na pilha.
#run
aceita um argumento que é um objeto que responde a #call
e retorna uma resposta do rack (uma instância de Rack::Response
).
Conclusões
Usando Rack::Builder
Você pode construir cadeias de middlewares e qualquer solicitação para o seu aplicativo será processada por cada middleware, por sua vez, antes de finalmente ser processada pela peça final da pilha (no nosso caso, MessageApp
). Isso é extremamente útil porque separa diferentes estágios das solicitações de processamento. Em termos de 'separação de preocupações', não poderia ser muito mais limpo!
Você pode construir um 'oleoduto de solicitação' composto por vários meios de idade que lidam com coisas como:
- Autenticação
- Autorização
- Cache
- Decoração
- Monitoramento de desempenho e uso
- Execução (na verdade lide com a solicitação e forneça uma resposta)
(acima dos pontos da bala de outra resposta neste tópico)
Você frequentemente verá isso em aplicações profissionais de Sinatra. Sinatra usa rack! Ver aqui Para a definição do que Sinatra É!
Como nota final, nosso config.ru
pode ser escrito em um estilo de curta distância, 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 '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
Links Úteis
Rack - a interface B/W Web e App Server
O rack é um pacote Ruby que fornece uma interface para um servidor da 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 do middleware fica entre o cliente e o servidor, processando solicitações de entrada e respostas de saída.
Em palavras leigos, é 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 responde ao método de chamada, tomando o hash do ambiente como um parâmetro e retornando uma matriz com três elementos:
- O código de resposta HTTP
- Um hash de cabeçalhos
- o corpo de resposta, que deve responder a cada solicitar.
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 config.ru como um arquivo de rack, você pode executar qualquer arquivo de rack com rackup
comando. E a porta padrão para isso é 9292
. Para testar isso, você pode simplesmente correr rackup
no diretório do seu Rails e veja o resultado. Você também pode atribuir a porta na qual deseja executá -la. Comando para executar o arquivo rack em qualquer porta específica é
rackup -p PORT_NUMBER