Rackミドルウェアとは何ですか?


267

RubyのRackミドルウェアとは何ですか?「ミドルウェア」が何を意味するのかについて、良い説明が見つかりませんでした。


4
RailsGuideには、ミドルウェアを含めてラックを包括的にカバーするガイドもあります。guides.rubyonrails.org
xji

PhusionPassengerチームに感謝します。彼らのブログには、よく説明された記事があります。rubyraptor.org/...
拉麺

ラックおよびラックミドルウェアについては、この記事で説明しています。ラックベースのアプリケーションの作成についても説明しました。
shashwat srivastava

回答:


353

設計としてのラック

Rackミドルウェアは、「リクエストとレスポンスをフィルタリングする方法」以上のものです。これは、Rackを使用したWebサーバーのパイプライン設計パターンの実装です。

要求を処理するさまざまな段階を非常に明確に分離します。問題の分離は、適切に設計されたすべてのソフトウェア製品の主要な目標です。

たとえば、Rackを使用すると、パイプラインの個別のステージを実行できます。

  • 認証:リクエストが届いたときに、ユーザーのログオン詳細は正しいですか?このOAuth、HTTP基本認証、名前/パスワードを検証するにはどうすればよいですか?

  • 承認:「ユーザーには、この特定のタスクを実行する権限がありますか?」、つまりロールベースのセキュリティ。

  • キャッシング:このリクエストはすでに処理しましたが、キャッシュされた結果を返すことはできますか?

  • 装飾:下流の処理を改善するためのリクエストをどのように強化できますか?

  • パフォーマンスと使用状況の監視:リクエストとレスポンスからどのような統計を取得できますか?

  • 実行:実際に要求を処理し、応答を提供します。

さまざまなステージを分離できること(およびオプションでそれらを含めることができること)は、適切に構造化されたアプリケーションの開発に非常に役立ちます。

コミュニティ

また、Rack Middlewareを取り巻く素晴らしいエコシステムが開発されています。事前に構築されたラックコンポーネントを見つけて、上記のすべての手順を実行できるようになるはずです。ミドルウェアのリストについては、Rack GitHub wikiを参照してください。

ミドルウェアとは?

ミドルウェアは恐ろしい用語であり、何らかのタスクの実行を支援するが、直接関与していないソフトウェアコンポーネント/ライブラリを指します。非常に一般的な例は、ロギング、認証、その他の一般的な水平処理コンポーネントです。これらは、多くの場合、複数のアプリケーションにわたって誰もが必要とするものですが、あまり多くの人々が自分自身の構築に関心を持っている(またはすべきである)ものではありません。

詳しくは


私が明確にしていないことの1つは、すべてのミドルウェアが同じデータを共有するかどうかです。セキュリティのためにそれらを分離すること(つまり、サンドボックス1)は可能ですか?
Brian Armstrong

2
ラックはアプリケーションの一部であるため、すべてのミドルウェアがリクエストの同じコピーを構成し、それぞれが希望する方法でそれを変更できます。私の知る限り、同じプロセス内でオブジェクトを別のオブジェクトからサンドボックス化する方法と同じ方法でそれらをサンドボックス化する方法はありません(にもかかわらずRubyのサンドボックス化を試みます)。
Chris McCauley

1
ラックはレーキとは異なることを理解してください。
Manish Shrivastava 2014年

1
私はミドルウェアを、自分がコーディングしたものと、サーバーとの間でやり取りされるものとの間にある、アプリの真ん中にあるものと考えたいと思います。これは、ラックスペースでホストされています。「ラックミドルウェア」という用語がわかりにくいのは、ご存じのように、2000年以上前にオリジナルのラックミドルウェアをすべて書いたのは孔子だったからです。フランスでは。
LpLrich、2015年

74

まず最初に、Rackは正確に2つあります。

  • Webサーバーインターフェイスの規則
  • 宝石

ラック-Webサーバーインターフェイス

ラックの基本は単純な規則です。すべてのラック準拠のWebサーバーは、指定したオブジェクトのcallメソッドを常に呼び出し、そのメソッドの結果を提供します。Rackは、この呼び出しメソッドがどのように見える必要があるか、また何を返す必要があるかを正確に指定します。それはラックです。

簡単に試してみましょう。私はWEBrickをラック準拠のWebサーバーとして使用しますが、どれでも使用できます。JSON文字列を返す簡単なWebアプリケーションを作成してみましょう。このために、config.ruというファイルを作成します。config.ruは、ラックに準拠したWebサーバーでconfig.ruのコンテンツを実行するだけの、rack gemのコマンドrackupによって自動的に呼び出されます。それでは、config.ruファイルに以下を追加しましょう:

class JSONServer
  def call(env)
    [200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
  end
end

map '/hello.json' do
  run JSONServer.new
end

規約に指定されているように、サーバーには環境ハッシュを受け入れ、Webサーバーが提供する[ステータス、ヘッダー、本文]という形式の配列を返すcallというメソッドがあります。ラックアップを呼び出すだけで試してみましょう。デフォルトのラック準拠サーバー、おそらくWEBrickまたはMongrelが起動し、要求が処理されるのをすぐに待ちます。

$ 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

urlまたはhttp://localhost:9292/hello.jsonvoilaをカーリングまたはアクセスして、新しいJSONサーバーをテストしてみましょう。

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

できます。すごい!RailsであろうとSinatraであろうと、それがすべてのWebフレームワークの基礎です。ある時点で、それらは呼び出しメソッドを実装し、すべてのフレームワークコードを処理し、最後に典型的な[ステータス、ヘッダー、本文]形式で応答を返します。

たとえばRuby on Railsでは、ラックリクエストActionDispatch::Routing.Mapperは次のようなクラスにヒットします。

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

したがって、基本的にRailsはルートが一致するかどうかをenvハッシュに依存してチェックします。その場合は、envハッシュをアプリケーションに渡して応答を計算し、そうでない場合はすぐに404で応答します。したがって、ラックインターフェース規則に準拠しているすべてのWebサーバーは、完全に機能するRailsアプリケーションにサービスを提供できます。

ミドルウェア

Rackは、ミドルウェアレイヤーの作成もサポートしています。彼らは基本的にリクエストを傍受し、それを使って何かを実行します。これは、用途の広いタスクに非常に役立ちます。

リクエストの所要時間も測定するロギングをJSONサーバーに追加するとします。これを行うミドルウェアロガーを作成するだけです。

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

作成されると、実際のラックアプリケーションのコピーが保存されます。私たちの場合、それはJSONServerのインスタンスです。Rackはミドルウェアのcallメソッドを自動的に呼び出し、[status, headers, body]JSONServerが返すように配列を返します。

したがって、このミドルウェアでは、開始点が取られ、JSONServerへの実際の呼び出しがで行われ@app.call(env)、ロガーがロギングエントリを出力し、最後にとして応答を返します[@status, @headers, @body]

私たちの小さなrackup.ruがこのミドルウェアを使用するようにするには、次のように使用するRackLoggerを追加します。

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   

サーバーを再起動して出来上がり。リクエストごとにログを出力します。ラックを使用すると、呼び出される複数のミドルウェアを追加順に追加できます。これは、ラックアプリケーションのコアを変更せずに機能を追加するための優れた方法です。

ラック-宝石

ラック-まず第一に-は慣習ですが、優れた機能を提供する宝石でもあります。JSONサーバーで既に使用しているものの1つであるrackupコマンド。しかし、まだまだあります!ラック宝石は、静的ファイルやディレクトリ全体の提供など、多くのユースケースに対応できる小さなアプリケーションを提供します。htmls / index.htmlにある非常に基本的なHTMLファイルなど、単純なファイルを提供する方法を見てみましょう。

<!DOCTYPE HTML>
  <html>
  <head>
    <title>The Index</title>
  </head>

  <body>
    <p>Index Page</p>
  </body>
</html>

このファイルをウェブサイトのルートから提供したいので、config.ruに以下を追加します。

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

訪問http://localhost:9292すると、htmlファイルが完全にレンダリングされていることがわかります。簡単でしたよね?

/ javascriptsの下にいくつかのJavaScriptファイルを作成し、以下をconfig.ruに追加して、JavaScriptファイルのディレクトリ全体を追加してみましょう。

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

サーバーを再起動してアクセスするhttp://localhost:9292/javascriptと、今すぐどこからでも含めることができるすべてのJavaScriptファイルのリストが表示されます。


3
しかし、ラックミドルウェアではありませんか?
Rupの

1
ラックが何であるかわからない場合は、このブログ投稿を読んだ後それが何でありどのように使用するかが正確にわかります。非常に素晴らしい。ただし、皮肉なことに、投稿の最後にある公式のラックドキュメントへのリンクは利用できなくなりました。
コリン

あなたの権利、ありがとう。投稿にコンテンツを含め、デッドリンクを削除しました。
Thomas Fankhauser 2017

慣習ではないと思います。これはインターフェースであり、要求/応答モデルに対して明確に定義された契約です
Ron Klein

20

私は自分のラックをかなりの時間理解するのに問題がありました。このミニチュアRuby Webサーバーを自分で作成するように取り組んだ後、私はそれを完全に理解しました。ここで私のブログ(http://gauravchande.com/what-is-rack-in-ruby-rails)で(ストーリーの形で)ラックに関する私の知識を共有しました

フィードバックは大歓迎です。


13
Stack Overflowではリンクのみの回答はお勧めしません。リンク先のリソースが将来利用できなくなると、回答が役に立たなくなるためです。少なくともブログ投稿の関連するポイントを要約し、この回答に追加してください。

投稿ありがとうございます。私は非常に初心者のRailsプログラマーで、明確な投稿でラックのコンセプトを理解しました。
Eduardo Ramos

素晴らしいブログ投稿。他の答えは、もう少し複雑なIMOのようです。
ハマグリ2015

なんて素晴らしい説明でしょう。ありがとう、ガウラフ。
rovitulli

7

config.ru 最小限の実行可能な例

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)

実行rackupしてにアクセスしてくださいlocalhost:9292。出力は次のとおりです。

main
Middleware

したがってMiddleware、メインアプリをラップして呼び出すことは明らかです。したがって、要求を前処理し、応答を後処理することができます。

http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stackで説明されているように、Railsは多くの機能にRackミドルウェアを使用しており、config.middleware.useファミリーメソッドを使用して独自のミドルウェアを追加することもできます。

ミドルウェアに機能を実装する利点は、Railsだけでなく、あらゆるRackフレームワーク、つまりすべての主要なRubyのフレームワークで再利用できることです。


6

ラックミドルウェアは、アプリケーションに送信される要求と応答をフィルタリングする方法です。ミドルウェアコンポーネントはクライアントとサーバーの間にあり、インバウンド要求とアウトバウンド応答を処理しますが、それはWebサーバーとの通信に使用できるインターフェース以上のものです。通常はRubyクラスであるモジュールをグループ化して順序付けし、モジュール間の依存関係を指定するために使用されます。ラックミドルウェアモジュールは、次の条件のみを満たしている必要があります。–スタック内の次のアプリケーションをパラメーターとして取るコンストラクターがあること–環境呼び出しをパラメーターとして取る「呼び出し」メソッドに応答すること この呼び出しからの戻り値は、ステータスコード、環境ハッシュ、応答本文の配列です。


4

Rackミドルウェアを使用して、いくつかの問題を解決しました。

  1. カスタムラックミドルウェアを使用してJSON解析エラーをキャッチし、クライアントがバストされたJSONを送信すると、適切にフォーマットされたエラーメッセージを返す
  2. Rack :: Deflaterによるコンテンツの圧縮

どちらの場合もかなりエレガントな修正が行われました。


2
この回答は、いくらか有用ではありますが、Rack Middlewareが何であるという質問には実際には対応していません。

また、これはかなりリンクのみの回答です...:P
Smar

4

ラックとは?

Rackは、RubyをサポートするWebサーバーとRubyフレームワークの間の最小限のインターフェースを提供します。

Rackを使用すると、Rackアプリケーションを作成できます。

ラックは、環境ハッシュ(クライアントからのHTTPリクエスト内に含まれる、CGIのようなヘッダーで構成されるハッシュ)をラックアプリケーションに渡します。ラックアプリケーションは、このハッシュに含まれるものを使用して、必要なことをすべて実行できます。

ラックアプリケーションとは

Rackを使用するには、「アプリ」を提供する必要があります。これは#call、環境ハッシュをパラメーターとして使用してメソッドに応答するオブジェクトです(通常はとして定義されますenv)。#call正確に3つの値の配列を返す必要があります。

  • ステータスコード(たとえば、「200」)、
  • ヘッダのハッシュ
  • レスポンスボディ(Rubyのメソッドに応答しなければなりませんeach)。

そのような配列を返すラックアプリケーションを作成できます。これは、レスポンス内でラックによってクライアントに送り返されます(これは実際にはクラスのインスタンスになりRack::Responseます[ドキュメントに移動するにはクリックしてください])。

非常にシンプルなラックアプリケーション:

  • gem install rack
  • config.ruファイルを作成する-ラックはこれを探すことを知っています。

Rack::Responseレスポンス本文がString:を含む配列であるレスポンス(のインスタンス)を返す小さなラックアプリケーションを作成します"Hello, World!"

コマンドを使用してローカルサーバーを起動しますrackup

ブラウザで関連するポートにアクセスすると、「Hello、World!」と表示されます。ビューポートにレンダリングされます。

#./message_app.rb
class MessageApp
  def call(env)
    [200, {}, ['Hello, World!']]
  end
end

#./config.ru
require_relative './message_app'

run MessageApp.new

でローカルサーバーを起動しrackuplocalhost:9292にアクセスすると、「Hello、World!」が表示されます。レンダリング。

これは包括的な説明ではありませんが、基本的にここで発生することは、クライアント(ブラウザ)がローカルサーバー経由でHTTPリクエストをラックに送信し、ラックがインスタンス化MessageAppして実行callし、環境ハッシュをパラメーターとしてメソッドに渡します(env引数)。

Rackは戻り値(配列)を受け取り、それを使用してのインスタンスを作成し、それをRack::Responseクライアントに送り返します。ブラウザはマジックを使用して「Hello、World!」を出力します 画面に。

ちなみに、環境ハッシュがどのように見えるかを確認したい場合は、単にputs envその下に置いてくださいdef call(env)

最小限、ここに書いたのはラックアプリケーションです!

Rackアプリケーションを着信環境ハッシュと相互作用させる

小さなRackアプリでは、envハッシュを操作できます(環境ハッシュの詳細については、こちらを参照してください)。

ユーザーが独自のクエリ文字列をURLに入力する機能を実装するため、その文字列はHTTPリクエストに存在し、環境ハッシュのキー/値ペアの1つの値としてカプセル化されます。

私たちのRackアプリはEnvironmentハッシュからそのクエリ文字列にアクセスし、それをレスポンスのBodyを介してクライアント(この場合はブラウザ)に送り返します。

EnvironmentハッシュのRackドキュメントから: 「QUERY_STRING:?に続くリクエストURLの部分(ある場合)。空の場合がありますが、常に必要です!」

#./message_app.rb
class MessageApp
  def call(env)
    message = env['QUERY_STRING']
    [200, {}, [message]]
  end
end

次に、(クエリ文字列である)にrackupアクセスすると、ビューポートにレンダリングされた 'hello'が表示されます。localhost:9292?hello?hello

ラックミドルウェア

私達はします:

  • Rackミドルウェアをコードベースに挿入します-クラス:MessageSetter
  • 環境ハッシュは最初にこのクラスにヒットし、パラメーターとして渡されます:env
  • MessageSetter'MESSAGE'キーがenvハッシュに挿入されます。その値は空の'Hello, World!'場合env['QUERY_STRING']です。env['QUERY_STRING']そうでない場合、
  • 最後に、それは戻ります@app.call(env)- @app「スタック」の次のアプリですMessageApp

まず、「ロングハンド」バージョン:

#./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

ラック:: Builderのドキュメント我々はその参照Rack::Builder実装繰り返し構造のラックアプリケーションに小さなDSLを。これは基本的に、1つ以上のミドルウェアで構成される「スタック」と、ディスパッチ先の「最下位」アプリケーションを構築できることを意味します。最下位レベルのアプリケーションを通過するすべてのリクエストは、最初にミドルウェアによって処理されます。

#useスタックで使用するミドルウェアを指定します。引数としてミドルウェアを使用します。

ラックミドルウェアは次の条件を満たす必要があります。

  • スタック内の次のアプリケーションをパラメーターとして取るコンストラクターがあります。
  • call環境ハッシュをパラメーターとして受け取るメソッドに応答します。

私たちの場合、「ミドルウェア」はMessageSetter、「コンストラクタ」はMessageSetterのinitializeメソッド、スタック内の「次のアプリケーション」はMessageAppです。

したがって、ここでは、内部で何Rack::Builderが行われるかにより、のメソッドのapp引数はになります。MessageSetterinitializeMessageApp

(先に進む前に、上記の周りに頭を向けてください)

したがって、ミドルウェアの各部分は、既存の環境ハッシュをチェーン内の次のアプリケーションに「受け渡し」ます。そのため、スタック内の次のアプリケーションに渡す前に、ミドルウェア内でその環境ハッシュを変更する機会があります。

#runに応答し#call、Rack Response(のインスタンスRack::Response)を返すオブジェクトである引数を取ります。

結論

これを使用Rack::Builderすると、ミドルウェアのチェーンを構築でき、アプリケーションへのリクエストは各ミドルウェアによって順番に処理されてから、スタックの最後のピース(この場合はMessageApp)によって最終的に処理されます。これは、リクエストの処理の異なる段階を分離するため、非常に便利です。「懸念の分離」に関しては、それはそれほどクリーンではありません!

次のようなことに対処するいくつかのミドルウェアで構成される「リクエストパイプライン」を構築できます。

  • 認証
  • 認可
  • キャッシング
  • デコレーション
  • パフォーマンスと使用状況の監視
  • 実行(実際に要求を処理し、応答を提供する)

(このスレッドの別の回答からの箇条書きの上)

これは、プロのSinatraアプリケーションでよく見られます。シナトラはラックを使用しています!参照してくださいここで何シナトラの定義については、IS

最後の注意として、私たちconfig.ruは短縮形で書くことができ、まったく同じ機能を生成します(これが通常表示されるものです)。

require_relative './message_app'
require_relative './middleware/message_setter'

use MessageSetter
run MessageApp.new

そして、何MessageAppをしているのかをより明確に示すために、必要な3つの引数を使用しての#call新しいインスタンスを作成していることを明示的に示した「ロングハンド」バージョンRack::Responseを次に示します。

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

役立つリンク


1

ラック-インターフェースb / wウェブとアプリサーバー

Rackは、アプリケーションと通信するためのWebサーバーのインターフェースを提供するRubyパッケージです。Webサーバーとアプリの間にミドルウェアコンポーネントを追加して、リクエスト/レスポンスの動作を変更するのは簡単です。ミドルウェアコンポーネントはクライアントとサーバーの間にあり、インバウンド要求とアウトバウンド応答を処理します。

簡単に言うと、これは基本的に、サーバーとRailsアプリ(またはその他のRuby Webアプリ)が相互に通信する方法に関する一連のガイドラインにすぎません

Rackを使用するには、「app」を提供します。これは、callメソッドに応答し、環境ハッシュをパラメーターとして受け取り、3つの要素を持つ配列を返すオブジェクトです。

  • HTTP応答コード
  • ヘッダーのハッシュ
  • レスポンスボディそれぞれに対応しなければならない、要求

詳細については、以下のリンクをたどってください。

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

Railsでは、config.ruをラックファイルとして使用しており、rackupコマンドを使用して任意のラックファイルを実行できます。これのデフォルトのポートは9292です。これをテストするには、単にrackuprailsディレクトリで実行して結果を確認します。実行するポートを割り当てることもできます。特定のポートでラックファイルを実行するコマンドは

rackup -p PORT_NUMBER

1

ユニコーンとレールの間にあるラックを示す画像

Rackは、HTTPリクエスト/レスポンスを抽象化するシンプルなインターフェースを提供する宝石です。ラックは、アダプターとしてWebフレームワーク(Rails、Sinatraなど)とWebサーバー(ユニコーン、プーマ)の間にあります。上の画像から、これはユニコーンサーバーをレールについて知ることから完全に独立させ、レールはユニコーンについて知らないからです。これは良い例である疎結合関心の分離

上の画像は、このhttps://youtu.be/3PnUV9QzB0gでの Railsカンファレンスの講演からのものです。理解を深めるために、この動画を視聴することをおすすめします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.