Router
Was ist Routing?
Router definiert, welche Middleware und welcher Handler eine HTTP-Anfrage verarbeitet. Dies ist die grundlegendste und zentrale Funktionalität in Salvo.
Intern besteht ein Router im Wesentlichen aus einer Reihe von Filtern. Wenn eine Anfrage eintrifft, testet der Router sich selbst und seine Nachfolger der Reihe nach von oben nach unten, um zu prüfen, ob sie die Anfrage abgleichen können. Wenn eine Übereinstimmung erfolgreich ist, wird die Middleware auf der gesamten Kette, die durch den Router und seine Nachfolger-Router gebildet wird, sequenziell ausgeführt. Wenn während der Verarbeitung der Response-Status auf einen Fehler (4XX, 5XX) oder eine Umleitung (3XX) gesetzt wird, werden nachfolgende Middleware und Handler übersprungen. Sie können auch manuell ctrl.skip_rest() aufrufen, um die verbleibende Middleware und den Handler zu überspringen.
Während des Abgleichvorgangs existiert ein URL-Pfadinformationsobjekt, das als ein Objekt betrachtet werden kann, das während des Abgleichs vollständig von Filtern verbraucht werden muss. Wenn alle Filter in einem bestimmten Router erfolgreich übereinstimmen und diese URL-Pfadinformationen vollständig verbraucht wurden, gilt dies als "erfolgreicher Abgleich".
Beispiel:
Ist tatsächlich äquivalent zu:
Wenn auf GET /articles/ zugegriffen wird, gilt dies als erfolgreicher Abgleich und list_articles wird ausgeführt. Wenn jedoch auf GET /articles/123 zugegriffen wird, schlägt der Routenabgleich fehl und gibt einen 404-Fehler zurück, weil Router::with_path("articles") nur den /articles-Teil der URL-Pfadinformationen verbraucht, den /123-Teil jedoch unverbraucht lässt, daher gilt der Abgleich als fehlgeschlagen. Um einen erfolgreichen Abgleich zu erreichen, kann die Route geändert werden zu:
Hier gleicht {**} jeden verbleibenden Pfad ab, sodass GET /articles/123 übereinstimmen und list_articles ausgeführt werden kann.
Flache Definition
Wir können Routen in einem flachen Stil definieren:
Baumartige Definition
Wir können Routen auch in einer baumartigen Struktur definieren, was der empfohlene Ansatz ist:
Diese Form der Definition macht Router-Definitionen hierarchisch, klar und einfach für komplexe Projekte.
Viele Methoden in Router geben nach dem Aufruf Self zurück, was das Schreiben von verkettetem Code erleichtert. Manchmal müssen Sie basierend auf bestimmten Bedingungen entscheiden, wie geroutet werden soll. Das Routing-System bietet auch die then-Funktion, die einfach zu verwenden ist:
Dieses Beispiel bedeutet, dass Routen zum Erstellen, Bearbeiten und Löschen von Artikeln nur hinzugefügt werden, wenn der Server im admin_mode ist.
Abrufen von Parametern aus Routen
Im obigen Code definiert {id} einen Parameter. Wir können seinen Wert über die Request-Instanz abrufen:
{id} stimmt mit einem Segment im Pfad überein. Normalerweise ist die id eines Artikels nur eine Zahl. In diesem Fall können wir einen regulären Ausdruck verwenden, um die Abgleichsregel für id einzuschränken, wie r"{id|\d+}".
Für numerische Typen gibt es eine noch einfachere Methode mit <id:num>. Spezifische Notationen sind:
{id:num}stimmt mit beliebig vielen Ziffernzeichen überein.{id:num[10]}stimmt genau mit einer bestimmten Anzahl von Ziffernzeichen überein; hier bedeutet 10, dass genau 10 Ziffern übereinstimmen.{id:num(..10)}stimmt mit 1 bis 9 Ziffernzeichen überein.{id:num(3..10)}stimmt mit 3 bis 9 Ziffernzeichen überein.{id:num(..=10)}stimmt mit 1 bis 10 Ziffernzeichen überein.{id:num(3..=10)}stimmt mit 3 bis 10 Ziffernzeichen überein.{id:num(10..)}stimmt mit mindestens 10 Ziffernzeichen überein.
Sie können auch alle verbleibenden Pfadsegmente mit {**}, {*+} oder {*?} abgleichen. Für eine bessere Code-Lesbarkeit können Sie geeignete Namen hinzufügen, um die Pfadsemantik klarer zu machen, z.B. {**file_path}.
{**}: Stellt einen Wildcard-Abgleich dar, bei dem der übereinstimmende Teil eine leere Zeichenkette sein kann. Zum Beispiel stimmt der Pfad/files/{**rest_path}mit/files,/files/abc.txt,/files/dir/abc.txtüberein.{*+}: Stellt einen Wildcard-Abgleich dar, bei dem der übereinstimmende Teil existieren muss und keine leere Zeichenkette sein kann. Zum Beispiel stimmt der Pfad/files/{*+rest_path}NICHT mit/filesüberein, aber mit/files/abc.txt,/files/dir/abc.txt.{*?}: Stellt einen Wildcard-Abgleich dar, bei dem der übereinstimmende Teil eine leere Zeichenkette sein kann, aber nur ein Pfadsegment enthalten darf. Zum Beispiel stimmt der Pfad/files/{*?rest_path}NICHT mit/files/dir/abc.txtüberein, aber mit/files,/files/abc.txt.
Mehrere Ausdrücke können kombiniert werden, um dasselbe Pfadsegment abzugleichen, z.B. /articles/article_{id:num}/, /images/{name}.{ext}.
Hinzufügen von Middleware
Middleware kann über die hoop-Funktion auf einem Router hinzugefügt werden:
In diesem Beispiel verwendet der Wurzelrouter check_authed, um zu überprüfen, ob der aktuelle Benutzer angemeldet ist. Alle Nachfolger-Router sind von dieser Middleware betroffen.
Wenn Benutzer nur writer-Informationen und Artikel durchsuchen, bevorzugen wir möglicherweise, dass sie ohne Anmeldung browsen können. Wir können die Routen wie folgt definieren:
Obwohl zwei Router dieselbe Pfaddefinition path("articles") haben, können sie trotzdem zum selben Elternrouter hinzugefügt werden.
Filter
Router verwendet intern Filter, um zu bestimmen, ob eine Route übereinstimmt. Filter unterstützen grundlegende logische Operationen mit or oder and. Ein Router kann mehrere Filter enthalten; wenn alle Filter erfolgreich übereinstimmen, stimmt die Route erfolgreich überein.
Website-Pfadinformationen sind eine Baumstruktur, aber diese Baumstruktur ist nicht äquivalent zur Baumstruktur, die Router organisiert. Ein Website-Pfad kann mehreren Router-Knoten entsprechen. Zum Beispiel erfordern einige Inhalte unter dem Pfad articles/ möglicherweise eine Anmeldung zum Anzeigen, während andere dies nicht tun. Wir können Teilpfade, die eine Anmeldeüberprüfung erfordern, unter einem Router mit Anmeldeüberprüfungs-Middleware organisieren und solche, die keine Überprüfung erfordern, unter einem anderen Router ohne sie:
Router verwenden Filter, um Anfragen zu filtern und sie an die entsprechende Middleware und Handler zur Verarbeitung zu senden.
path und method sind zwei der am häufigsten verwendeten Filter. path wird verwendet, um Pfadinformationen abzugleichen; method wird verwendet, um die Methode der Anfrage abzugleichen, z.B. GET, POST, PATCH usw.
Wir können Router-Filter mit and, or verbinden:
Pfadfilter
Filter basierend auf Anfragepfaden sind die am häufigsten verwendeten. Pfadfilter können Parameter definieren, zum Beispiel:
In einem Handler können sie über die get_param-Funktion des Request-Objekts abgerufen werden:
Methodenfilter
Filtert Anfragen basierend auf der Method der HTTP-Anfrage, zum Beispiel:
Hier sind get, patch, delete alles Methodenfilter. Dies ist tatsächlich äquivalent zu:
Benutzerdefinierte Wisp
Für bestimmte häufig auftretende Abgleichsausdrücke können wir über PathFilter::register_wisp_regex oder PathFilter::register_wisp_builder einen kurzen Namen zuweisen. Zum Beispiel erscheint das GUID-Format oft in Pfaden. Der normale Weg ist, es jedes Mal so zu schreiben, wenn ein Abgleich benötigt wird:
Dieses komplexe Regex jedes Mal zu schreiben, ist fehleranfällig und macht den Code weniger lesbar. Stattdessen können Sie dies tun:
Sie müssen es nur einmal registrieren. Danach können Sie direkt einfache Notation wie {id:guid} verwenden, um GUIDs abzugleichen, was das Code-Schreiben vereinfacht.
Kommend von einem Controller-basierten Web-Framework, wie versteht man Router?
Die Hauptunterschiede zwischen Routing-entworfenen Web-Frameworks (wie Salvo) und traditionellen MVC oder Controller-entworfenen Frameworks sind:
-
Flexibilität: Routing-Design ermöglicht eine flexiblere Definition von Anfrageverarbeitungsflüssen und ermöglicht eine präzise Kontrolle über die Logik für jeden Pfad. Zum Beispiel können Sie in Salvo direkt Handler-Funktionen für bestimmte Pfade definieren:
Im Controller-Design müssen Sie typischerweise zuerst eine Controller-Klasse definieren und dann mehrere Methoden innerhalb der Klasse definieren, um verschiedene Anfragen zu behandeln:
-
Middleware-Integration: Routing-Frameworks bieten normalerweise eine prägnantere Möglichkeit, Middleware zu integrieren, und ermöglichen es, Middleware auf bestimmte Routen anzuwenden. Salvos Middleware kann präzise auf bestimmte Routen angewendet werden:
-
Code-Organisation: Routing-Design neigt dazu, Code basierend auf Funktionalität oder API-Endpunkten zu organisieren, anstatt auf der Model-View-Controller-Schichtung von MVC. Routing-Design ermutigt, Code gemäß API-Endpunkt-Funktionalität zu organisieren: