web-dev-qa-db-ja.com

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

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

260
chrisgoyal

デザインとしてのラック

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

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

たとえば、Rackを使用すると、パイプラインの別々の段階で次のことができます。

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

  • Authorization:「ユーザーはこの特定のタスクの実行を許可されていますか?」、つまりロールベースのセキュリティ。

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

  • 装飾:ダウンストリーム処理を改善するために、どのようにリクエストを強化できますか?

  • パフォーマンスと使用状況の監視:要求と応答からどのような統計を取得できますか?

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

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

コミュニティ

また、ラックミドルウェアを中心に開発された優れたエコシステムもあります。上記のすべての手順を実行するために、構築済みのラックコンポーネントを見つけることができるはずです。 ミドルウェアのリストについてはRack GitHub wiki を参照してください。

ミドルウェアとは?

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

詳しくは

338
Chris McCauley

まず第一に、Rackはまさに2つのものです。

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

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

ラックの非常に基本的なことは、簡単な規則です。すべてのラック準拠Webサーバーは、ユーザーが与えたオブジェクトに対して常に呼び出しメソッドを呼び出し、そのメソッドの結果を提供します。ラックは、この呼び出しメソッドがどのように見え、どのように返されるかを正確に指定します。それがラックです。

簡単に試してみましょう。ラック準拠のWebサーバーとしてWEBrickを使用しますが、いずれでも使用できます。 JSON文字列を返す簡単なWebアプリケーションを作成しましょう。このために、config.ruというファイルを作成します。 config.ruは、ラックジェムのコマンドrackupによって自動的に呼び出され、ラック準拠のWebサーバーでconfig.ruの内容を実行します。 config.ruファイルに次を追加しましょう。

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

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

規約では、サーバーにはcallというメソッドがあり、環境ハッシュを受け入れて、Webサーバーが処理する[status、headers、body]という形式の配列を返します。単にrackupを呼び出して試してみましょう。デフォルトのラック準拠サーバー、おそらく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.jsonとvoilaにアクセスして、新しい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は、ミドルウェアの呼び出しメソッドを自動的に呼び出し、JSONServerが返すように、[status, headers, body]配列を返します。

したがって、このミドルウェアでは、開始点が取得され、@app.call(env)を使用してJSONServerの実際の呼び出しが行われ、ロガーはロギングエントリを出力し、最終的に応答を[@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   

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

ラック-宝石

ラックは、まず第一に慣例ですが、優れた機能を提供する宝石でもあります。それらの1つは、JSONサーバーで既に使用されているrackupコマンドです。しかし、もっとあります!ラックgemは、静的ファイルやディレクトリ全体の提供など、多くのユースケースに対してほとんどアプリケーションを提供しません。 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ファイルのリストが表示されます。

70

かなり長い間、ラックを自分で理解するのに問題がありました。 miniature Ruby web server 自分の作成に取り組んだ後、完全に理解しました。私のブログでRackについての学習(ストーリーの形で)を共有しました: http://gauravchande.com/what-is-rack-in-Ruby-Rails

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

19
Gaurav Chande

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だけでなく、すべての主要なRubyのRackフレームワークで再利用できることです。

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

6
L.Cole

ラックとは?

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

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

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

ラックアプリケーションとは何ですか?

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

  • ステータスコード(例: '200')、
  • ヘッダーのハッシュ
  • Response Body(Rubyメソッド、eachに応答する必要があります)。

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

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

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

応答(Rack::Responseのインスタンス)を返す小さなラックアプリケーションを作成します。応答ボディは、ストリング:"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

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

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

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

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

最小限ですが、ここで書いたのはRackアプリケーションです!

RackアプリケーションをIncoming Environmentハッシュと対話させる

小さなRackアプリでは、envハッシュとやり取りできます(環境ハッシュの詳細については here を参照してください)。

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

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

Environment HashのRackドキュメントから: "QUERY_STRING:?に続くリクエストURLの部分(存在する場合)。空でもかまいませんが、常に必要です!"

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

ここで、rackupにアクセスし、localhost:9292?hello?helloがクエリ文字列)にアクセスすると、ビューポートに「hello」と表示されます。

ラックミドルウェア

私達はします:

  • ラックミドルウェアをコードベースに挿入します-クラス: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

Rack :: Builder docs から、Rack::Builderが小さなDSLを実装して、Rackアプリケーションを繰り返し構築することがわかります。これは基本的に、1つ以上のミドルウェアとディスパッチ先の「ボトムレベル」アプリケーションで構成される「スタック」を構築できることを意味します。最下位レベルのアプリケーションに送信されるすべてのリクエストは、最初にミドルウェアによって処理されます。

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

ラックミドルウェアの要件:

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

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

したがって、ここでRack::Builderが内部で行うことにより、appMessageSetterメソッドのinitialize引数はMessageAppです。

(先に進む前に頭を上に向けてください)

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

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

結論

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

次のようなことを処理する複数のミドルウェアで構成される「要求パイプライン」を構築できます。

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

(このスレッドの別の回答からの箇条書きのポイントを超える)

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

最後の注意として、config.ruは簡単なスタイルで記述でき、まったく同じ機能を生成できます(これは通常表示されるものです)。

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

use MessageSetter
run MessageApp.new

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

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

便利なリンク

4
Andy

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

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

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

4
djcp

ラック-Webおよびアプリケーションサーバーとのインターフェイス

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

素人の言葉で言うと、基本的にはサーバーとRailsアプリ(または他のRubyウェブアプリ)が互いにどのように通信するかに関するガイドラインのセットにすぎません

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

  • HTTP応答コード
  • ヘッダーのハッシュ
  • 応答本体。各requestに応答する必要があります。

詳細については、以下のリンクをたどることができます。

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です。これをテストするには、Railsディレクトリでrackupを実行し、結果を確認します。実行するポートを割り当てることもできます。特定のポートでラックファイルを実行するコマンドは

rackup -p PORT_NUMBER
0
V K Singh