Was ist Rack-Middleware in Ruby? Ich konnte keine gute Erklärung dafür finden, was sie unter "Middleware" verstehen.
Was ist Rack-Middleware in Ruby? Ich konnte keine gute Erklärung dafür finden, was sie unter "Middleware" verstehen.
Antworten:
Rack-Middleware ist mehr als "eine Möglichkeit, eine Anfrage und Antwort zu filtern" - es ist eine Implementierung des Pipeline-Entwurfsmusters für Webserver, die Rack verwenden .
Die verschiedenen Phasen der Bearbeitung einer Anfrage werden sehr sauber voneinander getrennt. Die Trennung von Bedenken ist ein zentrales Ziel aller gut gestalteten Softwareprodukte.
Zum Beispiel kann ich mit Rack separate Phasen der Pipeline ausführen:
Authentifizierung : Sind die Anmeldedaten des Benutzers korrekt, wenn die Anforderung eingeht? Wie überprüfe ich diese OAuth, HTTP Basic Authentication, Name / Passwort?
Autorisierung : " Ist der Benutzer berechtigt, diese bestimmte Aufgabe auszuführen?", Dh rollenbasierte Sicherheit.
Caching : Habe ich diese Anfrage bereits bearbeitet? Kann ich ein zwischengespeichertes Ergebnis zurückgeben?
Dekoration : Wie kann ich die Anfrage verbessern, um die Weiterverarbeitung zu verbessern?
Leistungs- und Nutzungsüberwachung : Welche Statistiken kann ich aus der Anfrage und Antwort erhalten?
Ausführung : Behandeln Sie die Anforderung tatsächlich und geben Sie eine Antwort.
Die Möglichkeit, die verschiedenen Phasen zu trennen (und optional einzuschließen), ist eine große Hilfe bei der Entwicklung gut strukturierter Anwendungen.
Es gibt auch ein großartiges Ökosystem, das sich um Rack Middleware entwickelt - Sie sollten in der Lage sein, vorgefertigte Rack-Komponenten zu finden, um alle oben genannten Schritte und mehr auszuführen. Eine Liste der Middleware finden Sie im Rack GitHub-Wiki .
Middleware ist ein schrecklicher Begriff, der sich auf jede Softwarekomponente / Bibliothek bezieht, die bei der Ausführung einer Aufgabe hilft, aber nicht direkt daran beteiligt ist. Sehr häufige Beispiele sind Protokollierung, Authentifizierung und die anderen gängigen horizontalen Verarbeitungskomponenten . Dies sind in der Regel die Dinge, die jeder für mehrere Anwendungen benötigt, aber nicht zu viele Menschen sind daran interessiert (oder sollten es sein), sich selbst aufzubauen.
Der Kommentar, dass dies eine Möglichkeit zum Filtern von Anfragen ist, stammt wahrscheinlich aus der RailsCast-Episode 151: Rack Middleware- Bildschirmbesetzung.
Die Rack-Middleware wurde aus dem Rack heraus entwickelt und es gibt ein großartiges Intro unter Einführung in die Rack-Middleware .
Es gibt ein Intro auf Wikipedia über Middleware hier .
Zunächst einmal ist Rack genau zwei Dinge:
Rack - Die Webserver-Schnittstelle
Die Grundlagen des Racks sind eine einfache Konvention. Jeder Rack-kompatible Webserver ruft immer eine Aufrufmethode für ein Objekt auf, das Sie ihm geben, und liefert das Ergebnis dieser Methode. Rack gibt genau an, wie diese Aufrufmethode aussehen und wie sie zurückgegeben werden muss. Das ist Rack.
Probieren wir es einfach aus. Ich werde WEBrick als Rack-kompatiblen Webserver verwenden, aber jeder von ihnen wird es tun. Erstellen wir eine einfache Webanwendung, die eine JSON-Zeichenfolge zurückgibt. Dazu erstellen wir eine Datei namens config.ru. Die config.ru wird automatisch vom Befehlackup des Rack-Gem aufgerufen, der einfach den Inhalt der config.ru auf einem Rack-kompatiblen Webserver ausführt. Fügen wir der Datei config.ru also Folgendes hinzu:
class JSONServer
def call(env)
[200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
end
end
map '/hello.json' do
run JSONServer.new
end
Wie in der Konvention angegeben, verfügt unser Server über eine Methode namens call, die einen Umgebungs-Hash akzeptiert und ein Array mit der Form [Status, Header, Body] zurückgibt, die der Webserver bedienen soll. Probieren wir es einfach aus, indem wir Rackup anrufen. Ein Standard-Rack-kompatibler Server, möglicherweise WEBrick oder Mongrel, wird gestartet und wartet sofort auf Anfragen.
$ 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
Testen wir unseren neuen JSON-Server, indem wir entweder die URL http://localhost:9292/hello.json
und voila locken oder besuchen :
$ curl http://localhost:9292/hello.json
{ message: "Hello!" }
Es klappt. Toll! Das ist die Basis für jedes Webframework, sei es Rails oder Sinatra. Irgendwann implementieren sie eine Aufrufmethode, arbeiten den gesamten Framework-Code durch und geben schließlich eine Antwort in der typischen Form [Status, Header, Body] zurück.
In Ruby on Rails zum Beispiel treffen die Rack-Anforderungen die ActionDispatch::Routing.Mapper
Klasse, die so aussieht:
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
Grundsätzlich prüft Rails, abhängig vom Env-Hash, ob eine Route übereinstimmt. In diesem Fall wird der env-Hash an die Anwendung weitergeleitet, um die Antwort zu berechnen. Andernfalls antwortet er sofort mit einem 404. Jeder Webserver, der der Rack-Schnittstellenkonvention entspricht, kann eine vollständig ausgefüllte Rails-Anwendung bereitstellen.
Middleware
Rack unterstützt auch die Erstellung von Middleware-Schichten. Sie fangen im Grunde eine Anfrage ab, machen etwas damit und geben sie weiter. Dies ist sehr nützlich für vielseitige Aufgaben.
Angenommen, wir möchten unserem JSON-Server eine Protokollierung hinzufügen, die auch misst, wie lange eine Anforderung dauert. Wir können einfach einen Middleware-Logger erstellen, der genau dies tut:
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
Wenn es erstellt wird, speichert es sich selbst eine Kopie der eigentlichen Rack-Anwendung. In unserem Fall ist dies eine Instanz unseres JSONServers. Rack ruft automatisch die [status, headers, body]
Aufrufmethode für die Middleware auf und erwartet ein Array zurück, genau wie unser JSONServer zurückgibt.
In dieser Middleware wird also der Startpunkt festgelegt, der eigentliche Aufruf des JSONServers erfolgt mit @app.call(env)
, der Logger gibt den Protokollierungseintrag aus und gibt schließlich die Antwort als zurück [@status, @headers, @body]
.
Um unser kleines Rackup.ru dazu zu bringen, diese Middleware zu verwenden, fügen Sie einen RackLogger wie folgt hinzu:
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
Starten Sie den Server neu und voila, es gibt ein Protokoll bei jeder Anfrage. Mit Rack können Sie mehrere Middlewares hinzufügen, die in der Reihenfolge aufgerufen werden, in der sie hinzugefügt wurden. Es ist einfach eine großartige Möglichkeit, Funktionen hinzuzufügen, ohne den Kern der Rack-Anwendung zu ändern.
Rack - Der Edelstein
Obwohl Rack - vor allem - eine Konvention ist, ist es auch ein Juwel, das großartige Funktionalität bietet. Eine davon haben wir bereits für unseren JSON-Server verwendet, den Befehl rampup. Aber es gibt noch mehr! Das Rack-Juwel bietet kleine Anwendungen für viele Anwendungsfälle, z. B. das Bereitstellen statischer Dateien oder sogar ganzer Verzeichnisse. Mal sehen, wie wir eine einfache Datei bereitstellen, zum Beispiel eine sehr einfache HTML-Datei unter htmls / index.html:
<!DOCTYPE HTML>
<html>
<head>
<title>The Index</title>
</head>
<body>
<p>Index Page</p>
</body>
</html>
Wir möchten diese Datei möglicherweise über das Website-Stammverzeichnis bereitstellen. Fügen Sie daher Folgendes zu unserer config.ru hinzu:
map '/' do
run Rack::File.new "htmls/index.html"
end
Wenn wir besuchen http://localhost:9292
, sehen wir unsere HTML-Datei perfekt gerendert. Das war einfach, oder?
Fügen wir ein ganzes Verzeichnis von Javascript-Dateien hinzu, indem wir einige Javascript-Dateien unter / javascripts erstellen und der config.ru Folgendes hinzufügen:
map '/javascripts' do
run Rack::Directory.new "javascripts"
end
Starten Sie den Server neu und besuchen http://localhost:9292/javascript
Sie und Sie sehen eine Liste aller Javascript-Dateien, die Sie jetzt direkt von überall einschließen können.
Ich hatte ein Problem damit, Rack selbst für eine gute Zeit zu verstehen. Ich habe es erst vollständig verstanden, nachdem ich daran gearbeitet habe, diesen Miniatur-Ruby-Webserver selbst herzustellen. Ich habe meine Erkenntnisse über Rack (in Form einer Geschichte) hier in meinem Blog geteilt: http://gauravchande.com/what-is-rack-in-ruby-rails
Feedback ist mehr als willkommen.
config.ru
minimales lauffähiges Beispiel
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)
Laufen rackup
und besuchen localhost:9292
. Die Ausgabe ist:
main
Middleware
Es ist also klar, dass Middleware
die Haupt-App umbrochen und aufgerufen wird. Daher kann es die Anforderung vorverarbeiten und die Antwort auf irgendeine Weise nachbearbeiten.
Wie unter http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack erläutert , verwendet Rails Rack-Middleware für einen Großteil seiner Funktionalität, und Sie können auch eigene mit config.middleware.use
Familienmethoden hinzufügen .
Der Vorteil der Implementierung von Funktionen in einer Middleware besteht darin, dass Sie sie in jedem Rack-Framework wiederverwenden können, also in allen wichtigen Ruby-Frameworks und nicht nur in Rails.
Mit der Rack-Middleware können Sie eine Anfrage und Antwort filtern, die in Ihre Anwendung eingehen. Eine Middleware-Komponente befindet sich zwischen dem Client und dem Server und verarbeitet eingehende Anforderungen und ausgehende Antworten. Sie ist jedoch mehr als nur eine Schnittstelle, über die mit dem Webserver kommuniziert werden kann. Es wird verwendet, um Module, die normalerweise Ruby-Klassen sind, zu gruppieren und zu ordnen und die Abhängigkeit zwischen ihnen anzugeben. Das Rack-Middleware-Modul darf nur: - über einen Konstruktor verfügen, der die nächste Anwendung im Stapel als Parameter verwendet - auf die Aufrufmethode reagieren, die den Umgebungs-Hash als Parameter verwendet. Der von diesem Aufruf zurückgegebene Wert besteht aus folgenden Elementen: Statuscode, Umgebungs-Hash und Antworttext.
Ich habe Rack-Middleware verwendet, um einige Probleme zu lösen:
In beiden Fällen wurden ziemlich elegante Korrekturen vorgenommen.
Rack bietet eine minimale Schnittstelle zwischen Webservern, die Ruby- und Ruby-Frameworks unterstützen.
Mit Rack können Sie eine Rack-Anwendung schreiben.
Rack übergibt den Umgebungs-Hash (einen Hash, der in einer HTTP-Anforderung eines Clients enthalten ist und aus CGI-ähnlichen Headern besteht) an Ihre Rack-Anwendung, die die in diesem Hash enthaltenen Elemente verwenden kann, um zu tun, was sie wollen.
Um Rack verwenden zu können, müssen Sie eine 'App' bereitstellen - ein Objekt, das auf die #call
Methode mit dem Umgebungs-Hash als Parameter reagiert (normalerweise definiert als env
). #call
muss ein Array mit genau drei Werten zurückgeben:
each
).Sie können eine Rack-Anwendung schreiben, die ein solches Array zurückgibt. Diese wird von Rack innerhalb einer Antwort an Ihren Client zurückgesendet (dies ist tatsächlich eine Instanz der Klasse Rack::Response
[Klicken, um zu den Dokumenten zu gelangen]).
gem install rack
config.ru
Datei - Rack muss danach suchen.Wir werden eine winzige Rack-Anwendung erstellen, die eine Antwort (eine Instanz von Rack::Response
) zurückgibt , deren Antworttext ein Array ist, das eine Zeichenfolge enthält : "Hello, World!"
.
Wir werden einen lokalen Server mit dem Befehl starten rackup
.
Wenn Sie den entsprechenden Port in unserem Browser besuchen, sehen Sie "Hallo Welt!" im Ansichtsfenster gerendert.
#./message_app.rb
class MessageApp
def call(env)
[200, {}, ['Hello, World!']]
end
end
#./config.ru
require_relative './message_app'
run MessageApp.new
Starten Sie einen lokalen Server mit rackup
und besuchen Sie localhost: 9292, und Sie sollten "Hallo Welt!" gerendert.
Dies ist keine umfassende Erklärung, aber im Wesentlichen geschieht hier, dass der Client (der Browser) über Ihren lokalen Server eine HTTP-Anforderung an Rack sendet und Rack instanziiert MessageApp
und ausgeführt wird call
, wobei der Umgebungs-Hash als Parameter an die Methode übergeben wird ( das env
Argument).
Rack verwendet den Rückgabewert (das Array) und erstellt daraus eine Instanz von Rack::Response
und sendet diese an den Client zurück. Der Browser verwendet Magie , um "Hallo Welt!" Zu drucken. auf den Bildschirm.
Übrigens, wenn Sie sehen möchten, wie der Umgebungs-Hash aussieht, legen Sie ihn einfach puts env
darunter def call(env)
.
Minimal wie es ist, was Sie hier geschrieben haben, ist eine Rack-Anwendung!
In unserer kleinen Rack - App können wir mit dem interagieren env
Hash (siehe hier für mehr über das Umwelt - Hash).
Wir werden die Möglichkeit für den Benutzer implementieren, eine eigene Abfragezeichenfolge in die URL einzugeben. Daher ist diese Zeichenfolge in der HTTP-Anforderung vorhanden, die als Wert in einem der Schlüssel / Wert-Paare des Umgebungs-Hashs gekapselt ist.
Unsere Rack-App greift über den Umgebungs-Hash auf diese Abfragezeichenfolge zu und sendet sie über den Text in der Antwort an den Client (in diesem Fall unseren Browser) zurück.
Aus den Rack-Dokumenten zum Umgebungs-Hash: "QUERY_STRING: Der Teil der Anforderungs-URL, der auf das? Gefolgt wird, falls vorhanden. Kann leer sein, ist aber immer erforderlich!"
#./message_app.rb
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
Wenn Sie jetzt rackup
besuchen localhost:9292?hello
( ?hello
als Abfragezeichenfolge), sollte im Ansichtsfenster "Hallo" angezeigt werden.
Wir werden:
MessageSetter
,env
,MessageSetter
fügt einen 'MESSAGE'
Schlüssel in den env-Hash ein, dessen Wert 'Hello, World!'
if env['QUERY_STRING']
ist , wenn er leer ist; env['QUERY_STRING']
wenn nicht,@app.call(env)
- @app
als nächste App im 'Stack' : MessageApp
.Erstens die "Langhand" -Version:
#./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
Aus den Rack :: Builder-Dokumenten geht hervor , dass Rack::Builder
ein kleines DSL implementiert wird , um Rack-Anwendungen iterativ zu erstellen. Dies bedeutet im Grunde, dass Sie einen "Stapel" erstellen können, der aus einem oder mehreren Middlewares und einer "untersten" Anwendung besteht, an die gesendet werden soll. Alle Anfragen, die an Ihre unterste Anwendung weitergeleitet werden, werden zuerst von Ihren Middleware (s) verarbeitet.
#use
Gibt die Middleware an, die in einem Stapel verwendet werden soll. Es nimmt die Middleware als Argument.
Rack Middleware muss:
call
Methode, die den Umgebungs-Hash als Parameter verwendet.In unserem Fall ist die 'Middleware' MessageSetter
, der 'Konstruktor' die MessageSetter- initialize
Methode, die 'nächste Anwendung' im Stapel MessageApp
.
Also hier, wegen dem , was Rack::Builder
tut unter der Haube, das app
Argument von MessageSetter
‚s - initialize
Methode ist MessageApp
.
(Machen Sie sich mit dem Kopf vertraut, bevor Sie fortfahren)
Daher gibt jede Middleware den vorhandenen Umgebungs-Hash im Wesentlichen an die nächste Anwendung in der Kette weiter. Sie haben also die Möglichkeit, diesen Umgebungs-Hash in der Middleware zu mutieren, bevor Sie ihn an die nächste Anwendung im Stapel weitergeben.
#run
Nimmt ein Argument, das ein Objekt ist, das auf #call
eine Rack-Antwort (eine Instanz von Rack::Response
) reagiert und diese zurückgibt .
Mit können Rack::Builder
Sie Ketten von Middlewares erstellen, und jede Anforderung an Ihre Anwendung wird von jeder Middleware nacheinander verarbeitet, bevor sie schließlich vom letzten Teil des Stapels (in unserem Fall MessageApp
) verarbeitet wird. Dies ist äußerst nützlich, da verschiedene Phasen der Verarbeitung von Anforderungen getrennt werden. In Bezug auf die Trennung von Bedenken könnte es nicht viel sauberer sein!
Sie können eine 'Anforderungspipeline' erstellen, die aus mehreren Middlewares besteht, die sich mit folgenden Dingen befassen:
(über den Aufzählungspunkten einer anderen Antwort in diesem Thread)
Sie werden dies häufig in professionellen Sinatra-Anwendungen sehen. Sinatra benutzt Rack! Sehen Sie hier für die Definition dessen , was Sinatra IST !
Als letzte Anmerkung config.ru
können wir in einem Kurzschriftstil geschrieben werden, der genau die gleiche Funktionalität erzeugt (und dies ist, was Sie normalerweise sehen werden):
require_relative './message_app'
require_relative './middleware/message_setter'
use MessageSetter
run MessageApp.new
Um genauer zu zeigen, was MessageApp
gerade getan wird, finden Sie hier die 'Langhand'-Version, die explizit zeigt, #call
dass eine neue Instanz von Rack::Response
mit den erforderlichen drei Argumenten erstellt wird.
class MessageApp
def call(env)
Rack::Response.new([env['MESSAGE']], 200, {})
end
end
Rack - Die Schnittstelle zwischen Web- und App-Server
Rack ist ein Ruby-Paket, das eine Schnittstelle für die Kommunikation eines Webservers mit der Anwendung bietet. Es ist einfach, Middleware-Komponenten zwischen dem Webserver und der App hinzuzufügen, um das Verhalten Ihrer Anfrage / Antwort zu ändern. Die Middleware-Komponente befindet sich zwischen dem Client und dem Server und verarbeitet eingehende Anforderungen und ausgehende Antworten.
In Laienwörtern handelt es sich im Grunde genommen nur um eine Reihe von Richtlinien, wie ein Server und eine Rails-App (oder eine andere Ruby-Web-App) miteinander kommunizieren sollen .
Um Rack zu verwenden, geben Sie eine "App" an: ein Objekt, das auf die Aufrufmethode reagiert, den Umgebungs-Hash als Parameter verwendet und ein Array mit drei Elementen zurückgibt:
Weitere Erklärungen finden Sie unter den folgenden Links.
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
In Rails haben wir config.ru als Rack-Datei. Sie können jede Rack-Datei mit dem rackup
Befehl ausführen . Und der Standardport dafür ist 9292
. Um dies zu testen, können Sie einfach rackup
in Ihrem Rails-Verzeichnis laufen und das Ergebnis sehen. Sie können auch einen Port zuweisen, auf dem Sie ihn ausführen möchten. Der Befehl zum Ausführen der Rack-Datei an einem bestimmten Port lautet
rackup -p PORT_NUMBER
Rack ist ein Juwel, das eine einfache Schnittstelle zur abstrakten HTTP-Anforderung / Antwort bietet. Das Rack befindet sich zwischen Web-Frameworks (Rails, Sinatra usw.) und Webservern (Unicorn, Puma) als Adapter. Auf dem obigen Bild bleibt der Einhornserver völlig unabhängig von der Kenntnis von Schienen, und die Schienen wissen nichts über Einhorn. Dies ist ein gutes Beispiel für lose Kopplung und Trennung von Bedenken .
Das obige Bild stammt von diesem Rails-Konferenzgespräch im Rack https://youtu.be/3PnUV9QzB0g. Ich empfehle, es für ein tieferes Verständnis anzusehen.