RubyのRackミドルウェアとは何ですか? 「ミドルウェア」とはどういう意味なのか、良い説明が見つかりませんでした。
ラックミドルウェアは、「リクエストとレスポンスをフィルタリングする方法」以上のものです。これは、 ラック を使用するWebサーバーの パイプライン設計パターン の実装です。
リクエストを処理するさまざまな段階を非常に明確に分離します。関心の分離は、適切に設計されたすべてのソフトウェア製品の主要な目標です。
たとえば、Rackを使用すると、パイプラインの別々の段階で次のことができます。
認証:リクエストが到着したとき、ユーザーのログオンの詳細は正しいですか?このOAuth、HTTP基本認証、名前/パスワードを検証するにはどうすればよいですか?
Authorization:「ユーザーはこの特定のタスクの実行を許可されていますか?」、つまりロールベースのセキュリティ。
キャッシング:このリクエストをすでに処理しましたが、キャッシュされた結果を返すことはできますか?
装飾:ダウンストリーム処理を改善するために、どのようにリクエストを強化できますか?
パフォーマンスと使用状況の監視:要求と応答からどのような統計を取得できますか?
Execution:実際に要求を処理し、応答を提供します。
さまざまな段階を分離できる(およびオプションでそれらを含めることができる)ことは、適切に構造化されたアプリケーションを開発する上で非常に役立ちます。
また、ラックミドルウェアを中心に開発された優れたエコシステムもあります。上記のすべての手順を実行するために、構築済みのラックコンポーネントを見つけることができるはずです。 ミドルウェアのリストについてはRack GitHub wiki を参照してください。
ミドルウェアは恐ろしい用語であり、何らかのタスクの実行を支援するが直接は関与しないソフトウェアコンポーネント/ライブラリを指します。非常に一般的な例は、ロギング、認証、およびその他の共通の水平処理コンポーネントです。これらは、複数のアプリケーションにまたがって誰もが必要とする傾向がありますが、自分自身の構築に関心がある(またはそうすべきではない)人はあまり多くありません。
リクエストをフィルタリングする方法であるというコメントは、おそらく RailsCastエピソード151:Rackミドルウェア スクリーンキャストから来ています。
RackミドルウェアはRackから進化したもので、 Introduction to Rackミドルウェア で素晴らしい紹介があります。
ウィキペディアにはミドルウェアの紹介があります こちら 。
まず第一に、Rackはまさに2つのものです。
ラック-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ファイルのリストが表示されます。
かなり長い間、ラックを自分で理解するのに問題がありました。 miniature Ruby web server 自分の作成に取り組んだ後、完全に理解しました。私のブログでRackについての学習(ストーリーの形で)を共有しました: http://gauravchande.com/what-is-rack-in-Ruby-Rails
フィードバックは大歓迎です。
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クラスであるモジュールのグループ化と順序付けに使用され、それらの間の依存関係を指定します。ラックミドルウェアモジュールは、以下のみを行う必要があります。–スタック内の次のアプリケーションをパラメーターとして取るコンストラクターを持つ–環境呼び出しをパラメーターとして取る「呼び出し」メソッドに応答するこの呼び出しから返される値は、ステータスコード、環境ハッシュ、応答本文の配列です。
Rackは、RubyフレームワークとRubyフレームワークをサポートするWebサーバー間の最小限のインターフェースを提供します。
Rackを使用すると、Rackアプリケーションを作成できます。
Rackは、Environmentハッシュ(CGIのようなヘッダーで構成されるクライアントからのHTTPリクエスト内に含まれるハッシュ)をRackアプリケーションに渡します。Rackアプリケーションは、このハッシュに含まれるものを使用して必要な処理を実行できます。
Rackを使用するには、「アプリ」、つまり、環境ハッシュをパラメーターとして使用する#call
メソッドに応答するオブジェクト(通常はenv
として定義される)を提供する必要があります。 #call
は、正確に3つの値の配列を返す必要があります。
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アプリでは、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
が内部で行うことにより、app
のMessageSetter
メソッドの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
Rackミドルウェアを使用して、いくつかの問題を解決しました。
どちらの場合も非常にエレガントな修正が行われました。
ラック-Webおよびアプリケーションサーバーとのインターフェイス
RackはRubyパッケージで、Webサーバーがアプリケーションと通信するためのインターフェースを提供します。 Webサーバーとアプリの間にミドルウェアコンポーネントを追加して、リクエスト/レスポンスの動作を変更するのは簡単です。ミドルウェアコンポーネントは、クライアントとサーバーの間に位置し、インバウンド要求とアウトバウンド応答を処理します。
素人の言葉で言うと、基本的にはサーバーとRailsアプリ(または他のRubyウェブアプリ)が互いにどのように通信するかに関するガイドラインのセットにすぎません。
Rackを使用するには、「アプリ」を提供します。呼び出しメソッドに応答し、環境ハッシュをパラメーターとして受け取り、3つの要素を持つ配列を返すオブジェクトです。
詳細については、以下のリンクをたどることができます。
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