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".

Foi útil?

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

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:

  1. Capturando erros de análise JSON com middleware de rack personalizado e retornar mensagens de erro bem formatadas quando o cliente envia JSON BUSTED
  2. 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 se env['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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top