Radiance ist eine Webanwendungsumgebung, die wie ein Web -Framework ähnelt, aber allgemeiner und flexiblerer ist. Sie sollten Sie persönliche Websites schreiben und allgemein bereitstellbare Anwendungen problemlos und so auf diese Weise so schreiben, dass sie auf praktisch jedem Setup verwendet werden können, ohne sich spezielle Anpassungen unterziehen zu müssen.
Radiance und zugehörige Module und Anwendungen werden über QuickLisp in einem separaten Dist verteilt. Um Radiance zu installieren, tun Sie:
(ql-dist:install-dist "http://dist.shirakumo.org/shirakumo.txt")
(ql:quickload :radiance)
Von da an sollten Sie in der Lage sein, jede Art von Radiance -Modul wie Purplish direkt über QuickLisps quickload zu laden und zu verwenden.
Sie finden ein Tutorial, das Radiance und die meisten wichtigen Konzepte einführt, und untersucht, wie Sie hier eine Webanwendung im Allgemeinen schreiben. Es sollte Ihnen ein gutes Gefühl dafür geben, wie Sie Dinge machen können, und Ihnen Hinweise darüber geben, wo Sie nachsehen sollen, wenn Sie eine bestimmte Funktion benötigen. Im letzten Teil wird es auch in die tatsächliche Einrichtung und Bereitstellung einer Strahlungsinstallation auf einem Produktionsserver eingehen.
Das grundlegendste, was Sie höchstwahrscheinlich tun möchten, ist eine Art HTML. Lassen Sie uns darauf hinarbeiten und allmählich erweitern. Bevor wir beginnen können, müssen wir Radiance starten.
( ql :quickload :radiance )
( radiance :startup) Wenn dies das erste Mal ist, dass Radiance eingerichtet ist, erhalten Sie mit dem r-welcome -Modul eine Notiz dazu. Es sollte Ihnen auch einen Link geben, den Sie in Ihrem Browser öffnen können, um eine kleine Grußseite zu sehen. Im Moment wollen wir nur unsere eigene kleine Seite daneben aufstellen.
( in-package :rad-user )
(define-page example " /example " ()
( setf (content-type *response* ) " text/plain " )
" Hi! " )Besuchen Sie Localhost: 8080/Beispiel sollte jetzt nur "Hallo" zeigen. In der Tat ziemlich langweilig. Lassen Sie uns stattdessen einige HTML ausspucken. Im Moment verwenden wir Cl-wer, da es sehr einfach ist. Laden Sie es zuerst und führen Sie es dann Folgendes aus:
(define-page example " /example " ()
( cl-who :with-html-output-to-string (o)
( cl-who :htm
( :html
( :head ( :title " Example Page " ))
( :body ( :header ( :h1 " Couldn't Be Simpler. " ))
( :main ( :p " Trust me on this one. " )))))))Eine Neukompilierung und Auffrischung später und wir haben ein Schriftstil. Als nächstes möchten wir wahrscheinlich eine CSS -Datei hinzufügen, um sie richtig zu stylen. Wir könnten dem CSS auch mit einer anderen Seite dienen, aber das ist nicht der beste Weg, um langfristig Dinge zu tun.
Schauen wir uns stattdessen an, wie Sie ein Modul erstellen, mit dem wir die Dinge ordentlicher organisieren können. Sie können die Dateien für ein Modul manuell erstellen. Im Moment werden wir uns jedoch mit einem automatisch generierten Skelett zusammenschließen, mit dem Sie Radiance bieten können.
(create-module " example " ) Es sollte Ihnen einen Pfad zurückgeben, auf dem sich das Modul befindet. Es sollte ein ASDF -System, eine Hauptlisp -Datei und zwei Ordner, static und template enthalten. Überraschenderweise enthält der static Ordner die Dateien statisch, und template für Vorlagendokumente, wenn Sie zufällig ein Vorlagensystem verwenden.
Öffnen wir das example.lisp LISP und tragen Sie unsere Beispielseite daraus über.
(define-page example " /example " ()
( cl-who :with-html-output-to-string (o)
( cl-who :htm
( :html
( :head ( :title " Example Page " ))
( :body ( :header ( :h1 " Couldn't Be Simpler. " ))
( :main ( :p " Trust me on this one. " ))))))) Seiten werden durch ein Namensymbol identifiziert. Da wir jetzt ein eigenes Modul und damit unser eigenes Paket haben, ist das Beispielsymbol oben nicht das gleiche wie das, das wir zuvor verwendet haben. Wir müssen die Seite nur im rad-user -Paket entfernen, um den Zusammenstoß zu vermeiden.
(remove-page ' rad-user::example) Stellen Sie sicher, dass Sie die Beispieldatei laden, wenn Sie sie jetzt ändern, damit die Änderungen wirksam werden. Lassen Sie uns als nächstes eine einfache CSS -Datei erstellen, um die Dinge ein wenig aufzupeppen. Die Datei wird example.css in den static Ordner platziert. Hier ist ein Beispiel -CSS, wenn Sie nicht Ihre eigenen schreiben möchten.
body {
font-family : sans-serif;
font-size : 12 pt ;
background : # EEE ;
}
header {
text-align : center;
}
main {
width : 800 px ;
margin : 0 auto 0 auto;
background : # FFF ;
padding : 10 px ;
border : 1 px solid # BBB ;
border-radius : 5 px ;
}Als nächstes müssen wir unsere HTML ändern, um tatsächlich mit dem Stilblatt zu verknüpfen. Um die Adresse an das Stylesheet zu bringen, müssen wir das Routing -System von Radiance verwenden. Machen Sie sich jedoch keine Sorgen, es ist kein großer Ärger.
(define-page example " /example " ()
( cl-who :with-html-output-to-string (o)
( cl-who :htm
( :html
( :head ( :title " Example Page " )
( :link :rel " stylesheet " :type " text/css "
:href (uri-to-url " /static/example/example.css " :representation :external )))
( :body ( :header ( :h1 " Couldn't Be Simpler. " ))
( :main ( :p " Trust me on this one. " ))))))) Aktualisieren Sie die Seite und Voilà, jetzt hat sie auch Pizzazz. Sie möchten wahrscheinlich eine Erklärung für das gesamte uri-to-url -Geschäft. Das gesamte Erläutern wird von den folgenden Abschnitten behandelt, aber das Kern ist, dass der Link zur statischen Datei unter jedem Setup ordnungsgemäß aufgelöst wird.
Eines der zentralsten Konzepte in Strahlung ist das eines URI. Ein URI ist ein Objekt, das aus einer Liste von Domänen, einer optionalen Portnummer und einem Pfad besteht (siehe uri ). Es handelt sich im Wesentlichen um eine abgespeckte Version eines allgemeinen URI, und als solches kein Schema, eine Abfrage oder ein Fragmententeil. Ein weiterer wichtiger Unterschied besteht darin, dass die domains -URIs an mehreren Punkten im gesamten Rahmen verwendet werden, um Standorte zu erfassen und die Versandanpassung zu verarbeiten.
Beachten Sie, dass URIs veränderlich sind. Dies ist wichtig für die Leistung, da URI -Modifikationen in mehreren Teilen auftreten müssen, die auf dem kritischen Weg liegen. Im üblichen Fall wird jedoch nicht erwartet, dass URIs außerhalb einigeriger ausgewählter Funktionen geändert werden. Das Ändern der Teile eines URI auf unerwartete Weise kann zu seltsamem Verhalten führen.
URIs haben eine einzigartige String -Darstellung und können serialisiert werden, um sie wieder in ein volles URI -Objekt zu analysieren. URIs können auch als Literale in FASL -Dateien abgeladen werden, sodass die Ausgabe von Makros in Ordnung ist. Die Syntax für einen URI lautet wie folgt:
URI ::= DOMAINS? (':' PORT)? '/' PATH?
DOMAINS ::= DOMAIN ('.' DOMAIN)*
DOMAIN ::= ('a'..'Z' | '0'..'9' | '-')
PORT ::= ('0'..'9'){1, 5}
PATH ::= .*
Sie können uri-to-url verwenden, um einen URI in eine konkrete URL zu verwandeln. Umkehrung, Codierung und ordnungsgemäße Formatierung aller Teile werden dort automatisch für Sie behandelt.
Siehe uri , domains , port , path , matcher , uri-string , make-uri , make-url ensure-uri , copy-uri , parse-uri , uri< , uri> , uri= , uri-matches , merge-uris , represent-uri , uri-to-url .
Um die Daten, die an und aus gesendet werden, zu verkörpern, haben wir die Idee eines Objekts von Anfrage ( request ) und Antwort ( response ). Das Anforderungsobjekt enthält die URI, die an welchem Ort die Anforderung angeht, und alle Daten, die in der HTTP -Nutzlast wie Post-, GET-, Header- und Cookie -Variablen enthalten sind. Das Antwortobjekt enthält den Rücklaufcode, die Header, Cookies und die tatsächlichen Körperdaten.
Während der Verarbeitung einer Anforderung müssen diese beiden Objekte immer vorhanden und an die Variablen *request* und *response* gebunden sein. Sie verkörpern viele sehr wichtige Informationen, die zur Generierung von dynamischen Seiten erforderlich sind. Darüber hinaus enthält die Anforderung eine undurchsichtige data , in der Sie willkürliche Daten speichern können. Dies ist nützlich, wenn Sie Informationen zwischen einzelnen Teilen des Systems austauschen müssen, die während der Anfrageausführung erreicht werden können.
Anfragen müssen nicht unbedingt vom HTTP -Server stammen. Um Dinge zu testen, können Sie auch eine Anfrage selbst erstellen und programmgesteuert senden. Wie auch immer, die primäre Schnittstelle zum Versenden einer Anfrage wird request . Dadurch wird ein Anforderungs- und Antwortobjekt für Sie erstellt und die URI angemessen behandelt. Wenn Sie dies selbst tun und wirklich nur ein vollständiges Anforderungsobjekt senden möchten, können Sie execute-request verwenden.
Für die tatsächliche Behandlung einer Anfrage siehe Dispatcher, Seiten und API -Endpunkte.
See *request* , *response* , *default-external-format* , *default-content-type* , request , uri , http-method , body-stream , headers , post-data , get-data , cookies , user-agent , referer , domain , remote , data , issue-time , response , data , return-code , content-type , external-format , headers , cookies , cookie , name , value , domain , path , expires , http-only , secure , cookie-header , cookie , get-var , post-var , post/get , header , file , redirect , serve-file , request-run-time , *debugger* , handle-condition , render-error-page , execute-request , set-data , request
Bevor eine Anfrage angesandt werden kann, durchläuft sie so etwas, das das Routing -System genannt wird. Im Gegensatz zu anderen Frameworks, in denen 'Routen' festlegen, was mit einer Anfrage umgeht, ist eine Route ( route ) eine Form des URI -Übersetzers. Dieser Teil des Systems ist das, was für die Erstellung und Aufrechterhaltung von zwei "Universen" verantwortlich ist, eine interne und eine externe.
Das interne Universum ist das eigentliche Webanwendungen, in dem das externe Universum der HTTP -Server und ein Benutzer der Website lebt. Diese Unterscheidung ist erforderlich, damit Sie eine Hand schreiben können, ohne dass Sie sich Sorgen machen müssen, wie ein potenzieller Einrichtung auf einem Server irgendwann aussehen könnte. Sie müssen sich keine Sorgen darüber machen, welche Art von Domain, Port und Pfadeinrichtung möglicherweise erforderlich ist, um Ihre Bewerbung auszuführen. Auf der anderen Seite können Sie als Webadmin das System an Ihre genauen Wünsche anpassen und ausführen, ohne Angst zu haben, Dinge zu brechen.
Dies alles wird durch Routen erleichtert, von denen es zwei Arten gibt: Mapping- und Umkehrrouten. Die Mapping -Routen sind dafür verantwortlich, einen URI aus dem externen Universum in ein internes Universum zu verwandeln. Normalerweise beinhaltet dies das Abschneiden der Domäne auf der obersten Ebene und möglicherweise eine Zuordnung von Subdomänen. Umkehrrouten tun das Gegenteil- sie gehen vom inneren Universum nach extern. Dies ist erforderlich, um Links auf Ihren servierten Seiten auf Ressourcen zu beziehen, die von außen tatsächlich zugänglich sind. Normalerweise beinhaltet dies die Umkehrung der Subdomain-Mapping und das Hinzufügen der obersten Domäne wieder.
Routen können willkürliche Arbeiten ausführen. Auf der grundlegendsten Ebene sind sie lediglich Funktionen, die eine URI in gewisser Weise ändern. Auf diese Weise können Sie ein sehr flexibles System erstellen, das leistungsfähig genug sein sollte, um alle Ihre Bedürfnisse als Administrator gerecht zu werden. Als Anwendungsschreiber müssen Sie nur sicherstellen, dass Sie für alle Links, die Sie in Ihre Seiten einfügen, external-uri oder uri-to-url verwenden.
Siehe route , name , direction , priority , translator , route , remove-route , list-routes , define-route , define-target-route define-matching-route -Route, define-string-route external-uri internal-uri
Schließlich kommen wir zu dem Teil, der tatsächlich Inhalte für eine Anfrage generiert. URI -Disponenten sind eine URI -Unterklasse, die auch einen Namen, eine Funktion und eine Priorität tragen. Das Leben in einer vorrangig sortierten Liste, die bei Einstieg einer Anfrage verarbeitet wird. Der URI der Anfrage wird gegen jeden Dispatcher abgestimmt. Die Funktion des ersten Dispatcher, der übereinstimmt, wird dann ausgeführt.
Und das war's. Die Funktion des Dispatcher ist dafür verantwortlich, die erforderlichen Werte im Antwortobjekt so festzulegen, dass der Seiteninhalt bereitgestellt wird. Dazu kann dies entweder das data des Antwortobjekts direkt festlegen oder Sie einen geeigneten Wert aus der Funktion zurückgeben. Radiance akzeptiert nur vier Arten von Werten: stream , pathname , string und (array (unsigned-byte 8)) .
Wenn ein URI -Dispatcher keine explizite Prioritätszahl hat, wird seine Priorität gegenüber anderen durch die Spezifität des URI bestimmt. In der URI -Sortierfunktion uri> finden Sie eine Erklärung, wie genau dies berechnet wird.
Siehe uri-dispatcher , name , dispatch-function , priority , uri-dispatcher , remove-uri-dispatcher , list-uri-dispatchers , uri-dispatcher> , define-uri-dispatcher , dispatch
Seiten sind das, was Sie wahrscheinlich verwenden werden, um Ihre tatsächlichen Inhalte für die Servicefunktionen zu definieren. Eine Seite ist jedoch nur ein URI-Dispatcher mit zusätzlichen Funktionen im Definitionsmakro, das Ihnen die Sache erleichtert. Vor allem sind die erweiterbaren Optionen, für die Sie unten eine Erklärung finden können.
Es gibt einige Standardseiten, die von Radiance selbst eingerichtet wurden. Zuerst gibt es die favicon und robots , die einfach die jeweiligen Dateien aus static/ Verzeichnis von Radiance dienen. Sie möchten wahrscheinlich entweder Ihre eigenen Seiten dafür bereitstellen oder die Dateien auf Ihrem Produktionsserver aktualisieren.
Dann gibt es die static Seite, die dafür verantwortlich ist, statische Inhalte für alle Webanwendungen und Module zu bedienen. Es sollte auf jeder Domäne aktiv sein und immer auf dem Pfad /static/... wo ... ein Formular haben muss, in dem das erste Verzeichnis der Name eines Moduls ist, und der Rest ist ein Pfad innerhalb des static/ Verzeichnisses dieses Moduls. Auf diese Weise können Sie sich immer auf statische Dateien wie CSS, JS und Bilder über einen gemeinsamen Pfad beziehen.
Schließlich gibt es die api -Seite, die für die Behandlung des Versands der API -Endpunkte verantwortlich ist, die im folgenden Abschnitt erläutert werden. Die Seite wirkt ähnlich wie das statische, indem er den Pfad /api/... auf allen Domänen erfasst.
Siehe page , remove-page , define-page
Radiance bietet eine integrierte Unterstützung für die REST -API -Definition. Dies ist nicht nur eine angemessene Funktion, sondern auch, weil die meisten modernen Anwendungen eine Art API liefern möchten, und weil Radiance eine bestimmte Möglichkeit zum Schreiben Ihrer Anwendungen berät, die notwendigerweise API-Endpunkte beinhalten.
Konzeptionell sind API -Endpunkte Funktionen, die über eine Browseranforderung abgerufen werden können. Ihre Antwort wird dann auf ein vom Antragsteller lesbarer Format serialisiert, was auch immer das sein mag. Wichtig ist jedoch, dass API -Endpunkte sowohl von Benutzern als auch von Programmen verwendet werden sollten. Radiance fördert dies, da normalerweise jede Art von Aktion, die programmatisch über eine API ausgeführt werden kann, auch vom Benutzer auf irgendeine Weise ausgeführt werden muss. Um eine Duplikation zu vermeiden, können die beiden in Verbindung gebracht werden.
In der Regel sollten in der Regel jede Art von Datenmodifikationsmaßnahme über einen API -Endpunkt bereitgestellt werden, der etwas unterschiedlich reagiert, je nachdem, ob ein Benutzer oder eine Anwendungsanwendung ihn anfordert. Bei einem Benutzer sollte es normalerweise auf eine geeignete Seite zurückleiten und bei einer Anwendung eine Datennutzlast in einem lesbaren Format bereitstellen.
Der erste Teil davon ist das API -Formatsystem, das für die Serialisierung von Daten in ein bestimmtes Format verantwortlich ist. Standardmäßig wird nur ein S-expressionsbasiertes Format geliefert, aber ein Beitrag zum Get-JSON-Ausgang kann leicht geladen werden.
Der zweite Teil ist die Spezifikation des browser -Posts/des Parameters. Wenn dieser Parameter die genaue Zeichenfolge "true" enthält, wird die API -Anforderung als von einem Benutzer behandelt, und somit sollte eine Umleitung anstelle einer Datennutzlast ausgegeben werden.
Ihre Bewerbung sollte diese Dinge nutzen, um eine ordnungsgemäß integrierte API bereitzustellen. Jetzt besteht eine tatsächliche Endpunktdefinition aus einem Namen, einer Rohfunktion, einer Lambda-Liste, die die Argumente der Funktion beschreibt, und eine Anfrage-Parsing-Funktion. In der Regel sind nur erforderliche und optionale Argumente für Ihre Argumente sinnvoll. Eine HTTP -Anfrage hat schließlich nur "Keyword -Argumente", die sie bereitstellen kann, und diese können entweder vorhanden oder fehlen.
Der Name eines API -Endpunkts dient auch als Bezeichner, die Ihnen mitteilt, wo Sie ihn erreichen können. API -Endpunkte leben auf dem /api/ Pfad, gefolgt vom Namen des Endpunkts. Daher sind Sie dafür verantwortlich, dass Sie Ihre Endpunkte mit dem Namen Ihres Moduls oder Ihrer Anwendung vorfixieren, um versehentlich über andere Endpunkte zu stolpern. Dies ist anders als bei URI -Disponenten, da API -Endpunkte genau übereinstimmen und keine Mehrdeutigkeit oder Verarbeitung des Pfades zulassen. Somit muss jeder Endpunkt einen einzigartigen Pfad haben, der auch sofort als Name dienen kann.
Die RAW -Funktion ist die Funktion, für die die API eine Schnittstelle bereitstellt. Es ist verantwortlich für die Ausführung der angeforderten Aktion und die Rückgabe der entsprechenden Daten wie oben beschrieben. Weitere formatierte API-Daten finden Sie unter api-output . Zum Umleiten bei einer Browseranfrage siehe redirect .
Schließlich ist die Anfrage-Parsing-Funktion dafür verantwortlich, ein Anforderungsobjekt anzunehmen, die Argumente zu extrahieren, die die Funktion daraus benötigt, und schließlich diese Funktion mit den entsprechenden Argumenten aufzurufen- wenn möglich. Die Parsingfunktion kann einen api-argument-missing Fehler signalisieren, wenn ein erforderliches Argument fehlt. Überflüssige Argumente sollten ignoriert werden.
Sie können auch programmgesteuert einen API-Endpunkt mit call-api aufrufen oder einen Anforderungsaufruf mit call-api-request simulieren, ohne den gesamten URI-Versandmechanismus durchlaufen zu müssen.
Ähnlich wie bei Seiten akzeptieren API -Endpunktdefinitionen auch erweiterbare Optionen, die die Definition einfacher machen. Eine Erläuterung der Optionen finden Sie im folgenden Abschnitt.
Siehe api , *default-api-format* , *serialize-fallback* , api-format argslist remove-api-format handler remove-api-endpoint list-api-formats list-api-endpoints define-api define-api-format api-endpoint name call-api call-api-request api-output api-endpoint request-handler api-serialize
Optionen sind eine Möglichkeit, ein erweiterbares Definitionsmakro bereitzustellen. Dies ist nützlich, wenn ein Framework eine gemeinsame Möglichkeit bietet, etwas zu definieren, aber andere Teile möchten möglicherweise Erweiterungen dafür bereitstellen, um gemeinsame Operationen kürzer zu machen. Beispielsweise besteht eine gemeinsame Aufgabe darin, einen Seite oder einen API -Endpunkt auf Personen mit den erforderlichen Zugriffsanmeldeinformationen zu beschränken.
Um dies zu erleichtern, bietet Radiance einen eher generischen Optionsmechanismus. Die Optionen werden durch einen Optionsart unterteilt, der zu welchem Definitionsmakro die Option gehört. Radiance stellt die Optionstypen für api und page aus dem Feld aus.
Jede Option enthält ein Schlüsselwort für einen Namen und eine Expanderfunktion, die je nach Optionsart eine Reihe von Argumenten akzeptieren muss. Immer als Argumente sind der Name des definierten Dings, die Liste der Körperformulare der Definition und ein endgültiger, optionaler Wert, der der Option in der Optionsliste bereitgestellt wurde, falls dies überhaupt erwähnt wurde. Diese Expansionsfunktion ist dann für die Transformation der Körperformen des Definitionsmakros in irgendeiner Weise verantwortlich. Es kann auch eine zweite Form ausgeben, die außerhalb der Definition selbst platziert wird, damit die Umgebung auf irgendeine Weise eingerichtet wird.
Siehe option , option-type , name , expander , option , remove-option , list-options , define-option , expand-options
Das Konzept eines Moduls ist für Strahlung unerlässlich. Es dient als Darstellung eines "Teils" des Ganzen. Auf technischer Ebene ist ein Modul ein Paket mit speziellen Metadaten. Es wird vom modularize -System bereitgestellt und wird verwendet, um Haken und Auslöser, Schnittstellen und die Verfolgung einiger anderer Informationen zu erleichtern.
Für Sie bedeutet dies, dass Sie anstelle eines Standard- defpackage ein define-module -Formular verwenden sollten, um Ihr Primärpaket zu definieren. Die Syntax entspricht defpackage , enthält jedoch einige zusätzliche Optionen wie :domain , mit denen Sie die primäre Domäne angeben, auf der dieses Modul arbeiten soll (falls vorhanden).
Das Modulsystem ermöglicht auch das Binden eines ASDF -Systems an ein Modul. In diesem Fall wird das ASDF -System zu einem "virtuellen Modul". Um dies zu tun, müssen Sie Ihrer Systemdefinition drei Optionen hinzufügen:
:defsystem-depends-on (:radiance)
:class "radiance:virtual-module"
:module-name "MY-MODULE"
Dies ermöglicht es Radiance, ASDF -Systeminformationen in Ihrem Modul zu identifizieren und zu verknüpfen. Zur automatisierten Erstellung der erforderlichen System- und Moduldefinitionen für ein neues Modul siehe create-module .
See virtual-module , virtual-module-name , define-module , define-module-extension , delete-module , module , module-p , module-storage , module-storage-remove , module-identifier , module-name , current-module , module-domain , module-permissions , module-dependencies , module-required-interfaces , module-required-systems , module-pages , module-api-endpoints , describe-module , find-modules-directory , *modules-directory* , create-module
Einer der Mechanismen, die Strahlung bereitstellt, um die Integration von Modulen ineinander zu ermöglichen, sind Haken. Mit Hooks können Sie eine willkürliche Funktion als Antwort auf eine Art von Ereignis ausführen. Beispielsweise kann eine Forum -Software einen Haken einrichten, der ausgelöst wird, wenn ein neuer Beitrag erstellt wird. Eine Erweiterung könnte dann einen Auslöser an diesem Haken definieren, der zusätzliche Aufgaben ausführt.
Ein Haken kann eine willkürliche Anzahl von Triggern aufweisen, aber Sie sollten sicherstellen, dass ein Trigger nicht zu lang dauert, da das Auslösen eines Hakens ein Blockierungsvorgang ist, der erst abgeschlossen ist, wenn alle Trigger abgeschlossen sind. Daher kann ein langjähriger Triggeroperation eine Anforderungsantwort zu lange verzögern.
Manchmal sollten Hooks eher wie Schalter funktionieren, wo sie lange Zeit "eingeschaltet" werden können, bis sie später wieder "ausgeschaltet" werden. Wenn in dieser Zeit neue Trigger definiert werden, sollten sie automatisch aufgerufen werden. Dies ist es, was der define-hook-switch ermöglicht. Es produziert zwei Haken. Sobald der erste ausgelöst wurde, wird jeder Auslöser, der später definiert ist, automatisch aufgerufen, bis der zweite Haken ausgelöst wird. Dies ermöglicht Auslöser auf Hooks wie server-start können ordnungsgemäß funktionieren, auch wenn der Auslöser erst nach Beginn des Servers definiert ist.
Siehe list-hooks , define-hook , remove-hook , define-trigger , remove-trigger , trigger , define-hook-switch
Um nicht monolithisch zu werden, und um erweiterbare Backends zu ermöglichen, umfasst Radiance ein Schnittstellensystem. Im Allgemeinen versprechen eine Schnittstelle, wie einige Funktionen, Makros, Variablen usw. funktionieren sollten, sie jedoch nicht implementieren. Die tatsächliche Funktionalität, die alles macht, was die Schnittstelle umrichtet, wird in eine Implementierung gedrängt. Auf diese Weise können Benutzer gegen eine Schnittstelle codieren und ihre bereitgestellte Funktionen nutzen, ohne sich an ein bestimmtes Backend zu binden.
Angenommen, es gibt für ein konkretes Beispiel eine Schnittstelle für eine Datenbank. Dies ist sinnvoll, da es viele verschiedene Arten von Datenbanken gibt, die alle viele unterschiedliche Interaktionsweisen bieten, aber dennoch auch einige sehr häufige Vorgänge bieten: Daten speichern, Daten abrufen und die Daten ändern. Daher erstellen wir eine Schnittstelle, die diese gemeinsamen Operationen anbietet. Es liegt dann an einer Implementierung für eine bestimmte Art von Datenbank, damit die tatsächlichen Vorgänge funktionieren. Als Anwendungsschreiber können Sie dann die Datenbankschnittstelle verwenden und damit Ihre Anwendung automatisch mit vielen verschiedenen Datenbanken arbeiten.
Abgesehen davon, dass Anwendungsautoren einen Vorteil verschaffen, bedeutet die Entkopplung, die die Schnittstellen liefern, auch, dass ein Systemadministrator seine eigene Implementierung relativ leicht schreiben kann, wenn seine jeweiligen Anforderungen nicht durch bestehende Implementierungen erfüllt werden. Dank der Offenheit der Schnittstellen kann eine Implementierung sowohl eine Brücke zu etwas bieten, das im LISP -Prozess ausgeführt wird, als auch etwas, das völlig extern ist. Dadurch wird für den Administrator eines Produktionssystems eine Menge Auswahl offen, damit sie genau aussuchen können, was er benötigt.
In der Praxis sind Schnittstellen besondere Arten von Modulen und damit besondere Arten von Paketen. Als Teil ihrer Definition enthalten sie eine Reihe von Definitionen für andere Bindungen wie Funktionen, Variablen usw. Da es sich um ein Paket handelt, können Sie als Benutzer die Komponenten der Schnittstelle genau wie Sie in jedem anderen Paket etwas anderes verwenden. Es gibt keinen Unterschied. Als Implementierungsschreiber definieren Sie einfach alle Definitionen, die die Schnittstelle umrichtet.
Um ein Modul zu laden, das eine Schnittstelle verwendet, muss eine Implementierung für die Schnittstelle im Voraus geladen werden. Andernfalls könnten Makros nicht richtig funktionieren. Um abhängig von Schnittstellen in Ihrer ASDF -Systemdefinition zu ermöglichen, ohne sich auf eine bestimmte Implementierung beziehen zu müssen, liefert Radiance eine ASDF -Erweiterung. Diese Erweiterung ermöglicht es, Ihrer Liste eine Liste wie (:interface :foo) hinzuzufügen :depends-on der Liste. Radiance wird dann die Schnittstelle zu einer Betonimplementierung davon auflösen, wenn das Modul geladen wird.
Radiance bietet eine Reihe von Standardoberflächen. Jede dieser Schnittstellen verfügt über mindestens eine Standardimplementierung, die von Strahlungs-Contribs bereitgestellt wird. Die Schnittstellen sind:
adminauthbancachedatabaseloggermailprofilerateserversessionuserDie Schnittstellen werden unten ausführlich beschrieben.
Siehe interface , interface-p , implementation , implements , reset-interface , define-interface-extension , find-implementation , load-implementation , define-interface , define-implement-trigger
Um das Ausführen mehrerer Strahlungsinstanzen mit verschiedenen Setups auf derselben Maschine zu ermöglichen, bietet Radiance das, was es als Umgebungssystem nennt. Die Umgebung ist im Grunde die Konfigurations- und Laufzeitdateien für Strahlung selbst und alle geladenen Module. Die Strahlungskonfiguration enthält auch die Zuordnung der Schnittstelle zur ausgewählten Implementierung und entscheidet somit, was ausgewählt werden soll, wenn eine Schnittstelle angefordert wird.
Die jeweilige Umgebung, die verwendet wird, wird zuletzt ausgewählt, wenn startup aufgerufen wird, und das früheste, wenn ein Modul geladen wird. Im letzteren Fall werden interaktive Neustarts bereitgestellt, damit Sie eine Umgebung auswählen können. Dies ist notwendig, da sonst Strahlung die Schnittstellenzuordnung nicht beheben kann.
Als Teil des Umgebungssystems bietet Ihnen Radiance ein Konfigurationssystem, das Sie für Ihre Anwendung verwenden können. Es stellt sicher, dass die Einstellungen für jede Umgebung ordnungsgemäß multiplexiert sind und dass die Einstellungen immer anhaltend sind. Es verwendet auch ein menschliches lesbares Speicherformat, sodass die Dateien gelesen und geändert werden können, ohne dass spezielle Tools erforderlich sind.
Sehen Sie allgegenwärtig für die tatsächlichen Handhabung und Nutzungsstruktur des Konfigurationsspeichers. Beachten Sie nur, dass Strahlung anstelle der value config bietet.
Abgesehen von Konfigurationsdateien bietet die Umgebung auch konsistente Speicherorte für Laufzeitdatendateien wie Benutzer -Uploads, Cache -Dateien usw. Sie können diesen Ort mithilfe von environment-module-directory und environment-module-pathname abrufen. Beim Speichern von Uploads und Caches sollte ein Modul diese Pfade verwenden, um dem Administrator eine konsistente Schnittstelle zu präsentieren.
Bei einem bereitgestellten System könnte es erwünscht sein, den Standort der Umgebungsspeicherpfade zu ändern. In diesem Fall wird der Administrator aufgefordert, neue Methoden für environment-directory und environment-module-directory zur Anpassung des Verhaltens nach gewünschten Verhalten zu liefern. Weitere Informationen und Standardaktionen finden Sie auch in den zugehörigen Dokumentationszeichenfolgen.
Die Umgebung ermöglicht auch Administratorüberschreibungen. Verwenden der :static und :template -Typen für environment-module-directory erhalten Sie den Pfad, Dateien zu speichern, die die Standardvorlage und statische Dateien eines Moduls überschreiben sollten. Die Pfade in den jeweiligen Verzeichnissen müssen mit denen der eigenen Quelldateien des Moduls übereinstimmen. Beachten Sie, dass die statischen und Vorlagendateien, mit denen ein Modul tatsächlich verwendet wird, bei Modulladezeit zwischengespeichert werden, und sich somit nicht ändern, es sei denn, das Lisp-Bild wird neu gestartet oder die Quelldateien des Moduls werden neu geladen.
Siehe environment-change , environment , environment-directory , environment-module-directory , environment-module-pathname , check-environment , mconfig-pathname , mconfig-storage , mconfig , defaulted-mconfig , config , defaulted-config , template-file , @template , static-file , @static
Manchmal entwickeln sich Systeme auf nach hinten inkompatible Weise. In diesem Fall ist eine Laufzeit -Datenmigration erforderlich, in der vorhandene Setups weiter funktionieren. Radiance bietet ein System, um diesen Prozess zu automatisieren und ein reibungsloses Upgrade zu ermöglichen.
Die Migration zwischen den Versionen sollte automatisch während der Startsequenz von Radiance stattfinden. Als Administrator oder Autor sollten Sie keine zusätzlichen Schritte für Migrationen ausführen. Als Modulautor müssen Sie jedoch natürlich den Code bereitstellen, um die erforderlichen Datenmigrationsschritte für Ihr Modul auszuführen.
Damit ein Modul migratierbar ist, muss es von einem ASDF -System mit einer Versionspezifikation geladen werden. Die Version sollte dem Standardschema für gepunktete Nummern mit einem optionalen Versions -Hash folgen, der am Ende hinzugefügt werden kann. Sie können dann Migrationsschritte zwischen einzelnen Versionen definieren, indem Sie define-version-migration verwenden. Nach der Definition wird Radiance automatisch konkrete Versionen aufgenommen und die erforderlichen Migrationen nacheinander ausführen, um die aktuelle Zielversion zu erreichen. Weitere Informationen zum genauen Verfahren und zu Ihrer Möglichkeit finden Sie in migrate und migrate-versions .
Siehe last-known-system-version , migrate-versions , define-version-migration , ready-dependency-for-migration , ensure-dependencies-ready , versions , migrate
Schließlich bietet Radiance eine Standard -Start- und Herunterfahren -Sequenz, die sicherstellen sollte, dass die Dinge ordnungsgemäß eingerichtet und vorbereitet und danach wieder gut gereinigt werden. Ein großer Teil dieser Sequenz besteht nur darin, dass bestimmte Haken in der richtigen Reihenfolge und zu den richtigen Zeiten aufgerufen werden.
Während Sie einen Server mit der entsprechenden Schnittstellenfunktion manuell starten können, sollten Sie nicht erwarten, dass Anwendungen ordnungsgemäß ausgeführt werden, wenn Sie dies so tun. Viele von ihnen erwarten, dass bestimmte Haken gerufen werden, um ordnungsgemäß zu arbeiten. Aus diesem Grund sollten Sie immer, es sei denn, Sie wissen genau, was Sie tun, verwenden Sie startup und shutdown , um eine Radiance -Instanz zu verwalten. Die Dokumentation der beiden Funktionen sollte genau erklären, welche Haken ausgelöst werden und in welcher Reihenfolge. Eine Implementierung kann zusätzliche, nicht spezifizierte Definitionen für Symbole im Schnittstellenpaket liefern, solange diese Symbole nicht exportiert werden.
Siehe *startup-time* , uptime , server-start , server-ready , server-stop , server-shutdown , startup , startup-done , shutdown , shutdown-done , started-p
Diese Schnittstellen sind mit Strahlung verteilt und sind Teil des Kernpakets. Bibliotheken können jedoch zusätzliche Schnittstellen vorsehen. Für Implementierungen von Standardoberflächen sind die folgenden Relaxationen der Schnittstellendefinitionsbeschränkungen zulässig:
Die Lambda-Listen, die &key Argumente enthalten, können von weiteren, implementierenden Keyword-Argumenten erweitert werden. Lambda-Listen, die &optional , aber NO &key oder &rest enthalten, können durch weitere optionale Argumente erweitert werden. Lambda-Listen, die nur erforderliche Argumente enthalten, können durch weitere optionale oder Schlüsselwortargumente erweitert werden.
Diese Schnittstelle bietet eine Administrationsseite. Es sollte für jede Art von benutzerkonfigurierbaren Einstellungen oder für Systeminformationen verwendet werden. Beachten Sie, dass dies trotz "Verwaltung" nicht nur für Administratoren des Systems bestimmt ist. Die Seite sollte für jeden Benutzer verwendet werden.
Die Administrationsseite muss in der Lage sein, ein kategorisiertes Menü und ein Panel anzuzeigen. Die Panels werden von anderen Modulen bereitgestellt und können über admin:define-panel hinzugefügt werden. Panels, die den Zugriff auf sensible Operationen ermöglichen, sollten durch die Option :access und eine nicht defekten Genehmigung angemessen eingeschränkt werden. Eine Erläuterung der Berechtigungen finden Sie in der Benutzeroberfläche.
Verwenden Sie den page , um zur Verabreichung auf die Administrationsseite oder ein bestimmtes Panel zu verlinken.
Siehe admin:list-panels , admin:remove-panel , admin:define-panel , admin:panel
Die Authentifizierungsschnittstelle ist dafür verantwortlich, einen Benutzer an eine Anfrage zu binden. Aus diesem Grund muss es eine Art und Weise bereitstellen, mit der sich ein Benutzer gegen das System authentifizieren kann. Wie dies genau getan wird, liegt bei der Implementierung. Die Implementierung muss jedoch eine Seite bereitstellen, auf der der Authentifizierungsprozess eingeleitet wird. You can get a URI to it through the page resource and passing "login" as argument.
You can test for the user currently tied to the request by auth:current . This may also return NIL , in which case the user should be interpreted as being "anonymous" . See the user interface for more information.
See auth:*login-timeout* , auth:page , auth:current , auth:associate
This interface provides for IP-banning. It must prevent any client connecting through a banned IP from seeing the content of the actual page they're requesting. Bans can be lifted manually or automatically after a timeout. The implementation may or may not exert additional effort to track users across IPs.
See ban:jail , ban:list , ban:jail-time , ban:release
The cache interface provides for a generic caching mechanism with a customisable invalidation test. You can explicitly renew the cache by cache:renew . To define a cached block, simply use cache:with-cache , which will cause the cached value of the body to be returned if the test form evaluates to true.
The exact manner by which the cached value is stored is up to the implementation and cache:get or cache:with-cache may coerce the cached value to a string or byte array. The implementation may support any number of types of values to cache, but must in the very least support strings and byte arrays.
The name for a cached value must be a symbol whose name and package name do not contain any of the following characters: <>:"/|?*. The variant for a cached value must be an object that can be discriminated by its printed (as by princ ) representation. The same character constraints as for the name apply.
See cache:get , cache:renew , cache:with-cache
This interface provides you with a data persistence layer, usually called a database. This does not have to be a relational database, but may be one. In order to preserve implementation variance, only basic database operations are supported (no joins, triggers, etc). Data types are also restricted to integers, floats, and strings. Despite these constraints, the database interface is sufficiently useful for most applications.
Note that particular terminology is used to distance from traditional RDBMS terms: a schema is called a "structure". A table is called a "collection". A row is called a "record".
Performing database operations before the database is connected results in undefined behaviour. Thus, you should put your collection creation forms ( db:create ) within a trigger on db:connected . Radiance ensures that the database is connected while Radiance is running, so using the database interface in any page, api, or uri dispatcher definitions is completely fine.
The functions for actually performing data storage are, intuitively enough, called db:insert , db:remove , db:update , db:select , and db:iterate . The behaviour thereof should be pretty much what you'd expect. See the respective docstrings for a close inspection. Also see the docstring of db:create for a lengthy explanation on how to create a collection and what kind of restrictions are imposed.
The database must ensure that once a data manipulation operation has completed, the changes caused by it will be persisted across a restart of Radiance, the lisp image, or the machine, even in the case of an unforeseen crash.
See database:condition , database:connection-failed , database:connection-already-open , database:collection-condition , database:invalid-collection , database:collection-already-exists , database:invalid-field , database:id , database:ensure-id , database:connect , database:disconnect , database:connected-p , database:collections , database:collection-exists-p , database:create , database:structure , database:empty , database:drop , database:iterate , database:select , database:count , database:insert , database:remove , database:update , database:with-transaction , database:query , database:connected , database:disconnected
This interface provides primitive logging functions so that you can log messages about relevant happenings in the system. The actual configuration of what gets logged where and how is up to the implementation and the administrator of the system.
See logger:log , logger:trace , logger:debug , logger:info , logger:warn , logger:error , logger:severe , logger:fatal
With the mail interface you get a very minimal facility to send emails. A variety of components might need email access, in order to reach users outside of the website itself. The configuration of the way the emails are sent --remote server, local sendmail, etc.-- is implementation dependant.
The mail:send hook provided by the interface allows you to react to outgoing emails before they are sent.
See mail:send
The profile interface provides extensions to the user interface that are commonly used in applications that want users to have some kind of presence. As part of this, the interface must provide for a page on which a user's "profile" can be displayed. The profile must show panels of some kind. The panels are provided by other modules and can be added by profile:define-panel .
You can get a URI pointing to the profile page of a user through the page resource type.
The interface also provides access to an "avatar image" to visually identify the user ( profile:avatar ), a customisable name that the user can change ( profile:name ), and field types to what kind of data is contained in a user's field and whether it should be public information or not ( profile:fields profile:add-field profile:remove-field ).
See profile:page , profile:avatar , profile:name , profile:fields , profile:add-field , profile:remove-field , profile:list-panels , profile:remove-panel , profile:define-panel , profile:panel
This interface provides for a rate limitation mechanism to prevent spamming or overly eager access to potentially sensitive or costly resources. This happens in two steps. First, the behaviour of the rate limitation is defined for a particular resource by rate:define-limit . Then the resource is protected through the rate:with-limitation macro. If the access to the block by a certain user is too frequent, the block is not called, and the code in the limit definition is evaluated instead.
Note that rate limitation is per-client, -user, or -session depending on the implementation, but certainly not global.
See rate:define-limit , rate:left , rate:with-limitation
This and the logger interface are the only interfaces Radiance requires an implementation for in order to start. It is responsible for accepting and replying to HTTP requests in some manner. The implementation must accept requests and relay them to the Radiance request function, and then relay the returned response back to the requester.
Note that the actual arguments that specify the listener behaviour are implementation-dependant, as is configuration thereof. However, if applicable, the implementation must provide for a standard listener that is accessible on localhost on the port configured in (mconfig :radiance :port) and is started when radiance:startup is called.
See server:start , server:stop , server:listeners , server:started , server:stopped
The session interface provides for tracking a client over the course of multiple requests. It however cannot guarantee to track clients perfectly, as they may do several things in order to cloak or mask themselves or falsify information. Still, for most users, the session tracking should work fine enough.
The session interface is usually used by other interfaces or lower-lying libraries in order to provide persistence of information such as user authentication.
See session:*default-timeout* , session:session , session:= , session:start , session:get , session:list , session:id , session:field , session:timeout , session:end , session:active-p , session:create
This interface provides for persistent user objects and a permissions system. It does not take care of authentication, identification, tracking, or anything of the sort. It merely provides a user object upon which to build and with which permissions can be managed.
See user:user for a description of permissions and their behaviour.
See user:condition , user:not-found , user:user , user:= , user:list , user:get , user:id , user:username , user:fields , user:field , user:remove-field , user:remove , user:check , user:grant , user:revoke , user:add-default-permissions , user:create , user:remove , user:action , user:ready , user:unready
This is an extension of the database interface. Any module implementing this interface must also implement the database interface. This interface provides some extensions to allow more expressive database operations that are only directly supported by relational database systems.
See relational-database:join , relational-database:sql
*environment-root* has been removed as per issue #28 and fix #29. It has instead been replaced by a more generic mechanism for environment directories, incorporated by the function environment-directory . If you previously customised *environment-root* , please now change environment-directory instead, as described in §1.11.template-file and static-file .user:id identifier for each user object, allowing you to reference users in databases and records more efficiently.:unique on db:select and db:iterate . If you'd like to support the continued development of Radiance, please consider becoming a backer on Patreon: