Radiance est un environnement d'application Web, qui est un peu comme un cadre Web, mais plus général, plus flexible. Il devrait vous permettre d'écrire facilement des sites Web personnels et des applications généralement déployables et de manière à pouvoir être utilisées sur pratiquement n'importe quelle configuration sans avoir à subir des adaptations spéciales.
Le rayonnement et les modules et les applications associés sont distribués via QuickLisp dans un distant distant. Pour installer l'éclat, faites:
(ql-dist:install-dist "http://dist.shirakumo.org/shirakumo.txt")
(ql:quickload :radiance)
À partir de là, vous devriez pouvoir charger et utiliser tout type de module Radiance comme violon directement via quickload QuickLisp.
Vous pouvez trouver un tutoriel qui introduit le rayonnement et la plupart des concepts importants, et explore comment rédiger une application Web en général, ici. Cela devrait vous donner une bonne idée de la façon de procéder à des choses et de vous donner des conseils sur où chercher si vous avez besoin d'une fonctionnalité particulière. Dans la dernière partie, il ira également dans la configuration et le déploiement réels d'une installation Radiance sur un serveur de production.
La chose la plus élémentaire que vous voulez probablement faire est de servir une sorte de HTML. Alors travaillons vers cela et l'étendons progressivement. Avant de pouvoir commencer, nous devons démarrer l'éclat.
( ql :quickload :radiance )
( radiance :startup) Si c'est la première fois que la configuration de l'éclat, vous obtiendrez une note à ce sujet à l'aide du module r-welcome . Il devrait également vous donner un lien que vous pouvez ouvrir dans votre navigateur pour voir une petite page de salutation. Pour l'instant, nous voulons juste mettre notre propre petite page à côté.
( in-package :rad-user )
(define-page example " /example " ()
( setf (content-type *response* ) " text/plain " )
" Hi! " )Visiter LocalHost: 8080 / Exemple devrait maintenant afficher "Hi". Plutôt ennuyeux en effet. Alors, crachons à la place du HTML. Pour l'instant, nous utiliserons CL-WHO car c'est très simple. D'abord le téléchargez-le, puis exécutez ce qui suit:
(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. " )))))))Une recompilation et une rafraîchissement plus tard et nous avons du style de police en cours. Ensuite, nous voulons probablement y ajouter un fichier CSS pour le styliser correctement. Nous pourrions également servir le CSS en utilisant une autre page, mais ce n'est pas la meilleure façon de procéder à long terme.
Voyons plutôt comment créer un module, ce qui nous permettra d'organiser les choses de manière plus ordonnée. Vous pouvez créer les fichiers pour un module manuellement, mais pour l'instant, nous allons nous installer avec un squelette généré automatiquement que l'éclat peut vous fournir.
(create-module " example " ) Il devrait vous renvoyer un chemin sur lequel réside le module. Il doit contenir un système ASDF, un fichier LISP principal et deux dossiers, static et template . Étonnamment, le dossier static est l'endroit où les fichiers servis statiques vont, et template est destiné aux documents de modèle, si vous utilisez un système de modèle.
Ouvrez l' example.lisp .
(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. " ))))))) Les pages sont identifiées par un symbole de nom. Puisque nous avons maintenant notre propre module, et donc notre propre package, le symbole d'exemple ci-dessus ne sera pas le même que celui que nous avons utilisé auparavant. Nous devrons simplement supprimer la page dans le package rad-user pour éviter le choc.
(remove-page ' rad-user::example) Assurez-vous de charger le fichier d'exemple chaque fois que vous le modifiez maintenant pour que les modifications prennent effet. Créons ensuite un fichier CSS simple pour embellir un peu les choses. Le fichier sera example.css placé dans le dossier static . Voici un exemple de CSS si vous ne voulez pas écrire le vôtre.
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 ;
}Ensuite, nous devons modifier notre HTML pour créer un lien vers la feuille de style. Afin d'amener l'adresse à la feuille de style, nous devrons utiliser le système de routage de Radiance. Ne vous inquiétez pas cependant, ce n'est pas vraiment des tracas.
(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. " ))))))) Actualisez la page et voilà, maintenant il y a aussi du piquant. Vous voudrez probablement une explication de l'ensemble des activités uri-to-url . L'expliquer en totalité est géré par les sections qui suivent celui-ci, mais l'essentiel est qu'il garantit que le lien vers le fichier statique est correctement résolu sous toute configuration.
L'un des concepts les plus centraux de Radiance est celui d'un URI. Un URI est un objet qui se compose d'une liste de domaines, d'un numéro de port facultatif et d'un chemin (voir uri ). Il s'agit essentiellement d'une version dépouillée d'un URI général, et en tant que tel, n'inclut pas un schéma, une requête ou une partie de fragment. Une autre différence importante est que les uris domains sont utilisés à plusieurs points du cadre, à la fois pour capturer les emplacements et pour gérer la correspondance de répartition.
Notez que les URI sont mutables. Ceci est important pour les performances, car les modifications URI doivent se produire dans plusieurs parties qui se trouvent sur le chemin critique. Cependant, dans le cas habituel, il n'est pas prévu que les URI soient modifiés en dehors de quelques fonctions sélectionnées. La modification des parties d'un URI de manière inattendue peut conduire à un comportement étrange.
Les URI ont une représentation de chaîne unique et peuvent être sérialisés en chaîne et analysés à nouveau dans un objet URI complet. Les URI peuvent également être jetés dans des fichiers FASL en tant que littéraux, donc les émettre des macros est très bien. La syntaxe pour un URI est la suivante:
URI ::= DOMAINS? (':' PORT)? '/' PATH?
DOMAINS ::= DOMAIN ('.' DOMAIN)*
DOMAIN ::= ('a'..'Z' | '0'..'9' | '-')
PORT ::= ('0'..'9'){1, 5}
PATH ::= .*
Vous pouvez utiliser uri-to-url pour transformer un URI en URL de béton. L'inversion, le codage et la mise en forme appropriée de toutes les pièces sont gérés pour vous automatiquement.
Voir 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 .
Afin de résumer les données envoyées à et depuis, nous avons l'idée d'un objet de demande ( request ) et de réponse ( response ). L'objet de demande contient l'URI qui représente à quel emplacement la demande va, et toutes les données contenues dans la charge utile HTTP comme les variables post, get, en-tête et cookie. L'objet de réponse contient le code de retour, les en-têtes, les cookies et les données du corps réelles.
Pendant le traitement d'une demande, ces deux objets doivent toujours être présents et liés aux variables *request* et *response* . Ils résument beaucoup d'informations très vitales nécessaires pour générer des pages dynamiques. De plus, la demande contient un tableau data opaque dans lequel vous pouvez stocker des données arbitraires. Ceci est utile lorsque vous devez échanger des informations entre des parties individuelles du système qui peuvent être atteintes lors de l'exécution de la demande.
Les demandes ne doivent pas nécessairement provenir du serveur HTTP. Afin de tester des choses, vous pouvez également construire une demande vous-même et l'envoyer par programme. Quoi qu'il en soit, l'interface principale pour expédier une demande est appelée request . Cela construira un objet de demande et de réponse pour vous et gérera de manière appropriée l'URI. Si vous voulez le faire vous-même et envoyez-en vraiment un objet de demande complet, vous pouvez utiliser execute-request .
Pour le traitement réel d'une demande, voir les répartiteurs, les pages et les points de terminaison API.
Voir *request* , *response* , *default-external-format* , *default-content-type* content-type request , uri , http-method user-agent body-stream name headers , referer , post-data , get-data , domain , cookies value issue-time cookie response cookies data remote headers return-code , external-format data 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 , filet, demande de demande, set-data , request
Avant qu'une demande puisse être envoyée, elle passe par quelque chose appelé le système de routage. Contrairement à d'autres cadres, où les «routes» désignent ce qui gère une demande, en radiance, une route ( route ) est une forme de traducteur d'URI. Cette partie du système est ce qui est responsable de la création et du maintien de deux "univers", interne et externe.
L'univers interne est celui des applications Web réelles. Vous n'avez pas à vous soucier du type de domaine, du port, de la configuration de chemin peut être nécessaire pour exécuter votre application. D'un autre côté, il vous permet, en tant que webadmin, de personnaliser et d'exécuter le système vers vos désirs exacts sans crainte de briser les choses.
Tout cela est facilité par les itinéraires, dont il existe deux types: la cartographie et les routes d'inversion. Les itinéraires de cartographie sont responsables de la transformation d'un URI de l'univers externe en l'un des univers internes. Habituellement, cela implique de couper le domaine de niveau supérieur et peut-être de faire une cartographie des sous-domaines. Les voies d'inversion font le contraire - elles passent de l'univers interne à l'extérieur. Ceci est nécessaire pour établir des liens dans vos pages servies se réfèrent à des ressources qui sont réellement accessibles de l'extérieur. Habituellement, cela implique d'inverser la cartographie du sous-domaine et d'ajouter à nouveau le domaine de niveau supérieur.
Les itinéraires peuvent effectuer des travaux arbitraires. Au niveau le plus élémentaire, ce ne sont que des fonctions qui modifient un URI d'une manière ou d'une autre. Cela vous permet de créer un système très flexible qui devrait être suffisamment puissant pour répondre à tous vos besoins en tant qu'administrateur. En tant que rédacteur d'applications, il vous suffit de vous assurer d'utiliser external-uri ou uri-to-url sur tous les liens que vous mettez dans vos pages.
Voir route , name , direction , priority , translator , route , remove-route , list-routes , define-route , define-matching-route , define-target-route , define-string-route , internal-uri , external-uri
Enfin, nous arrivons à la pièce qui génère réellement du contenu pour une demande. Les répartiteurs URI sont une sous-classe d'URI qui porte également un nom, une fonction et une priorité. La liste en direct dans une liste triée de priorité, qui est traitée chaque fois qu'une demande arrive. L'URI de la demande est égalé à chaque répartiteur. La fonction du premier répartiteur qui correspond est ensuite exécutée.
Et c'est tout. La fonction du répartiteur est responsable de la définition des valeurs nécessaires dans l'objet de réponse pour fournir le contenu de la page. Pour ce faire, il peut soit définir directement le champ data de l'objet de réponse, soit renvoyer une valeur appropriée à partir de la fonction. Radiance n'accepte que quatre types de valeurs: stream , pathname , string et (array (unsigned-byte 8)) .
Si un répartiteur URI n'a pas de numéro de priorité explicite, sa priorité sur les autres est déterminée par la spécificité de l'URI. Voir la fonction de tri URI uri> pour une explication sur la façon dont cela est calculé exactement.
Voir uri-dispatcher , name , dispatch-function , priority , uri-dispatcher , remove-uri-dispatcher , list-uri-dispatchers , uri-dispatcher> , define-uri-dispatcher , dispatch
Les pages sont ce que vous utiliserez probablement pour définir vos fonctions de service de contenu réels. Cependant, une page est juste un dispatcher uri avec des fonctionnalités supplémentaires dans la macro de définition qui vous facilite les choses. Les options extensibles sont plus particulièrement, pour lesquelles vous pouvez trouver une explication ci-dessous.
Il y a quelques pages par défaut configurées par radiance elle-même. Il y a d'abord les pages favicon et robots , qui servent simplement les fichiers respectifs de Radiance's static/ Directory. Vous voudrez probablement fournir vos propres pages pour cela ou mettre à jour les fichiers sur votre serveur de production.
Ensuite, il y a la page static , qui est chargée de servir le contenu statique pour toutes les applications Web et modules. Il doit être actif sur n'importe quel domaine et toujours sur le chemin /static/... où ... doit avoir un formulaire où le premier répertoire est le nom d'un module, et le reste est un chemin dans le répertoire static/ de ce module. Cela vous permet de toujours être en mesure de vous référer à des fichiers statiques comme CSS, JS et des images par un chemin commun.
Enfin, il y a la page api , qui est responsable de la gestion de la répartition des points de terminaison de l'API, qui sont expliqués dans la section suivante. La page agit de manière similaire à celle statique en capturant le chemin /api/... sur tous les domaines.
Voir page , remove-page , define-page
Radiance fournit une prise en charge intégrée pour la définition de l'API REST. Ce n'est pas seulement une fonctionnalité clouée, mais plutôt parce que la plupart des applications modernes veulent fournir une API quelconque, et parce que Radiance conseille une certaine manière d'écrire vos applications qui impliquent nécessairement des points de terminaison de l'API.
Conceptuellement, les points de terminaison de l'API sont des fonctions qui sont appelables via une demande de navigateur. Leur réponse est ensuite sérialisée à un format lisible par le demandeur, quel qu'il soit. Il est important de se rappeler cependant que les points de terminaison de l'API doivent être utilisables par les utilisateurs et les programmes. Radiance encourage cela car généralement tout type d'action qui peut être effectué par programme via une API devra également être effectué par l'utilisateur d'une manière ou d'une autre. Afin d'éviter la duplication, les deux peuvent être confondus.
En tant que tel, tout type d'action de modification des données doit être fourni par un point de terminaison de l'API qui réagit légèrement différemment selon que l'utilisateur ou une application le demande. Dans le cas d'un utilisateur, il doit généralement rediriger vers une page appropriée, et dans le cas d'une application, elle devrait fournir une charge utile de données dans un format lisible.
La première partie de tout cela est le système de format API, qui est responsable de la sérialisation des données à un format spécifié. Par défaut, seul un format basé sur l'expression S est fourni, mais un contribution à obtenir la sortie JSON peut facilement être chargé.
La deuxième partie est la spécification du paramètre post / get browser . Si ce paramètre contient la chaîne exacte "true" , la demande d'API est traitée comme provenant d'un utilisateur, et donc une redirection plutôt que une charge utile de données doit être sortie.
Votre application doit utiliser ces choses afin de fournir une API correctement intégrée. Maintenant, une définition réelle du point de terminaison est composée d'un nom, d'une fonction brute, d'une liste lambda décrivant les arguments de la fonction et d'une fonction d'analyse de demande. En règle générale, pour vos arguments, seuls les arguments requis et facultatifs ont du sens. Après tout, une demande HTTP n'a que des "arguments de mots clés" qu'il peut fournir, et ceux-ci peuvent être présents ou manquants.
Le nom d'un point de terminaison API sert également d'identifiant qui vous indique où vous pouvez l'atteindre. Les points de terminaison de l'API vivent sur le /api/ PATH, suivi du nom du point de terminaison. En tant que tel, vous êtes responsable de la préfixation de vos points de terminaison avec le nom de votre module ou de votre application afin d'éviter de trébucher accidentellement sur d'autres points de terminaison. Cela est différent dans les répartiteurs URI, car les points de terminaison API doivent correspondre exactement et ne permettent aucune ambiguïté ou traitement du chemin. Ainsi, chaque point final doit avoir un chemin unique, qui peut également servir de nom immédiatement.
La fonction brute est la fonction pour laquelle l'API fournit une interface. Il est responsable de l'exécution de l'action demandée et du renvoi des données appropriées comme décrit ci-dessus. Pour retourner les données d'API formatées, voir api-output . Pour la redirection dans le cas d'une demande de navigateur, voir redirect .
Enfin, la fonction d'analyse de demande est chargée de prendre un objet de demande, d'extraire les arguments dont la fonction a besoin et enfin d'appeler cette fonction avec les arguments appropriés - si possible. La fonction d'analyse peut signaler une erreur api-argument-missing si un argument requis est manquant. Les arguments superflus doivent être ignorés.
Vous pouvez également appeler par programme un point de terminaison API à l'aide call-api , ou simuler un appel de demande avec call-api-request , sans avoir à passer par tout le mécanisme de répartition URI.
De façon similaire aux pages, les définitions de point de terminaison API acceptent également des options extensibles qui facilitent la définition. Voir la section suivante pour une explication des options.
Voir api , *default-api-format* , *serialize-fallback* argslist api-format , remove-api-format handler list-api-formats , Define remove-api-endpoint request-handler name define-api-format , list-api-endpoints api-output api-endpoint , API api-serialize call-api-request , call-api api-endpoint define-api
Les options sont un moyen de fournir une macro de définition extensible. Ceci est utile lorsqu'un cadre fournit un moyen courant de définir quelque chose, mais d'autres parties peuvent vouloir fournir des extensions à cela afin de rendre les opérations courantes plus courtes. Par exemple, une tâche commune consiste à restreindre une page ou un point de terminaison API aux personnes qui ont les informations d'accès requises.
Afin de faciliter cela, Radiance fournit un mécanisme d'options plutôt générique. Les options sont divisées par un type d'option qui désigne à quelle définition macro l'option appartient. Radiance fournit les types d'options api et page hors de la boîte.
Chaque option a un mot-clé pour un nom et une fonction d'expander qui doit accepter un certain nombre d'arguments, selon le type d'option. Les arguments sont toujours fournis comme le nom de la chose définie, la liste des formes corporelles de la définition et une valeur finale, facultative, qui a été fournie à l'option dans la liste des options, si elle a été mentionnée. Cette fonction d'expansion est alors responsable de la transformation des formes corporelles de la macro de définition d'une manière ou d'une autre. Il peut également émettre un deuxième formulaire placé en dehors de la définition elle-même, afin de permettre la configuration de l'environnement d'une manière ou d'une autre.
Voir option , option-type , name , expander , option , remove-option la liste, list-options , define-option , expand-options
Le concept d'un module est essentiel à l'éclat. Il sert de représentation d'une "partie" de l'ensemble. Sur le plan technique, un module est un package contenant des métadonnées spéciales. Il est fourni par le système modularize et est utilisé pour faciliter les crochets et les déclencheurs, les interfaces et le suivi de quelques autres informations.
Ce que cela signifie pour vous, c'est qu'au lieu d'un defpackage standard, vous devez utiliser un formulaire define-module pour définir votre package principal. La syntaxe est la même que defpackage , mais comprend des options supplémentaires comme :domain , qui vous permet de spécifier le domaine principal sur lequel ce module doit fonctionner (le cas échéant).
Le système de module permet également le lien d'un système ASDF à un module. Si cela est fait, le système ASDF devient un "module virtuel". Pour ce faire, vous devez ajouter trois options à la définition de votre système:
:defsystem-depends-on (:radiance)
:class "radiance:virtual-module"
:module-name "MY-MODULE"
Cela permet à Radiance d'identifier et d'associer les informations du système ASDF à votre module. Pour la création automatisée des définitions du système et du module nécessaires pour un nouveau module, voir create-module .
Voir virtual-module , virtual-module-name , define-module , define-module-extension , delete-module , module , current-module module-domain module-p , module-storage , module-permissions module-required-interfaces module-identifier module-dependencies module-storage-remove module-name module-required-systems , module-pages , module-api-endpoints , describe-module , find-modules-directory , *modules-directory* , create-module
L'un des mécanismes que Radiance fournit pour permettre d'intégrer les modules les uns dans les autres est les crochets. Les crochets vous permettent d'exécuter une fonction arbitraire en réponse à une sorte d'événement. Par exemple, un logiciel de forum peut configurer un crochet déclenché chaque fois qu'un nouveau message est créé. Une extension pourrait alors définir un déclencheur sur ce crochet qui effectue des tâches supplémentaires.
Un crochet peut avoir un nombre arbitraire de déclencheurs définis dessus, mais vous devez vous assurer qu'un déclencheur ne prend pas trop de temps, car le déclenchement d'un crochet est une opération de blocage qui ne se terminera pas tant que tous les déclencheurs se sont terminés. En tant que tel, une opération de déclenchement de longue durée peut retarder une réponse de demande trop longtemps.
Parfois, les crochets doivent fonctionner davantage comme des commutateurs, où ils peuvent être "allumés" pendant longtemps, jusqu'à ce qu'ils soient à nouveau désactivés plus tard. Si de nouveaux déclencheurs sont définis pendant cette période, ils doivent être appelés automatiquement. C'est ce que l'empilement de la define-hook-switch . Il produit deux crochets. Une fois le premier déclenché, tout déclencheur qui est défini dessus est appelé automatiquement jusqu'à ce que le deuxième crochet soit déclenché. Cela permet aux déclencheurs sur des crochets comme server-start de fonctionner correctement même si le déclencheur n'est défini qu'après le démarrage du serveur.
Voir list-hooks , define-hook , remove-hook , define-trigger , remove-trigger , trigger , define-hook-switch
Afin d'éviter de devenir monolithique et afin de permettre des backends extensibles, l'éclat comprend un système d'interface. Dans le sens le plus général, une interface fournit une promesse de savoir comment certaines fonctions, macros, variables, etc. devraient fonctionner, mais ne les implémente pas. La fonctionnalité réelle qui rend tout ce que l'interface décrit le travail est poussé vers une implémentation. Cela permet aux utilisateurs de coder contre une interface et d'utiliser ses fonctionnalités fournies, sans se lier à un backend particulier.
Pour un exemple concret, disons qu'il existe une interface pour une base de données. Ceci est raisonnable, car il existe de nombreux types de bases de données, qui offrent tous de nombreuses façons d'interaction différentes, mais toutes offrent également des opérations très courantes: le stockage des données, la récupération des données et la modification des données. Ainsi, nous créons une interface qui offre ces opérations communes. Il appartient ensuite à une implémentation pour un type spécifique de base de données pour que les opérations réelles fonctionnent. En tant que rédacteur d'applications, vous pouvez ensuite utiliser l'interface de la base de données, et avec elle, faire fonctionner automatiquement votre application avec de nombreuses bases de données différentes.
En plus de donner un avantage aux rédacteurs d'applications, le découplage que les interfaces fournissent signifient également qu'un administrateur système peut rédiger sa propre implémentation avec une facilité relative, si leurs exigences particulières ne sont pas satisfaites par les implémentations existantes. Grâce à l'opaqueté des interfaces, une implémentation peut à la fois fournir un pont à quelque chose qui s'exécute dans le processus LISP, et quelque chose qui est complètement externe. Cela laisse beaucoup de choix ouvert à l'administrateur d'un système de production pour leur permettre de choisir exactement ce dont ils ont besoin.
En pratique, les interfaces sont des types spéciaux de modules, et donc des types spéciaux de packages. Dans le cadre de leur définition, ils incluent une série de définitions pour d'autres liaisons comme les fonctions, les variables, etc. Puisqu'il s'agit d'un package, en tant qu'utilisateur, vous pouvez utiliser les composants de l'interface comme vous utiliseriez autre chose dans n'importe quel autre package. Il n'y a pas de différence. En tant qu'écrivain d'implémentation, vous redéfinissez ensuite toutes les définitions que l'interface décrit.
Afin de charger réellement un module qui utilise une interface, une implémentation de l'interface doit être chargée à l'avance. Sinon, les macros ne pouvaient pas fonctionner correctement. Ainsi, afin de permettre en fonction des interfaces dans votre définition du système ASDF sans avoir à se référer à une implémentation spécifique, Radiance fournit une extension ASDF. Cette extension permet d'ajouter une liste comme (:interface :foo) à votre liste :depends-on . Radiance résoudra ensuite l'interface à une implémentation en béton de celle-ci lorsque le module est chargé.
Radiance fournit un tas d'interfaces standard. Chacune de ces interfaces a au moins une implémentation standard fournie par Radiance-Contribs. Les interfaces sont:
adminauthbancachedatabaseloggermailprofilerateserversessionuserLes interfaces sont décrites en profondeur ci-dessous.
Voir interface , interface-p , implementation , implements , reset-interface , define-interface-extension , find-implementation , load-implementation , define-implement-trigger define-interface ,
Afin de permettre l'exécution de plusieurs instances d'éclat avec différentes configurations sur la même machine, Radiance fournit ce qu'il appelle un système d'environnement. L'environnement est essentiellement l'ensemble des fichiers de configuration et d'exécution pour Radiance lui-même et tous les modules chargés. La configuration de radiance comprend également le mappage de l'interface à l'implémentation choisie et décide donc de ce qui devrait être choisi si une interface est demandée.
L'environnement particulier utilisé est choisi au plus tard lorsque startup est appelé, et le plus tôt lorsqu'un module est chargé. Dans ce dernier cas, des redémarrages interactifs sont fournis pour vous permettre de choisir un environnement. Cela est nécessaire, car sinon l'éclat ne pourra pas résoudre le mappage d'interface.
Dans le cadre du système environnemental, Radiance vous fournit un système de configuration que vous pouvez - et devrait probablement - utiliser pour votre application. Il garantit que les paramètres sont correctement multiplexés pour chaque environnement et que les paramètres sont toujours persistants. Il utilise également un format de stockage lisible par l'homme, de sorte que les fichiers peuvent être lus et modifiés sans nécessiter d'outils spéciaux.
Voir omniprésent pour la manipulation et l'instructions réelles du stockage de configuration. Notez simplement qu'au lieu des fonctions value , Radiance fournit des fonctions config .
Outre les fichiers de configuration, l'environnement fournit également des emplacements de stockage cohérents pour les fichiers de données d'exécution, tels que les téléchargements d'utilisateurs, les fichiers de cache, etc. Vous pouvez récupérer cet emplacement en utilisant environment-module-directory et environment-module-pathname . Lors du stockage des téléchargements et des caches, un module doit utiliser ces chemins pour présenter une interface cohérente à l'administrateur.
Sur un système déployé, il pourrait être souhaité modifier l'emplacement des chemins de stockage de l'environnement, auquel cas l'administrateur est encouragé à fournir de nouvelles méthodes sur environment-directory et environment-module-directory pour personnaliser le comportement comme souhaité. Voir également les chaînes de documentation associées pour plus de détails et des actions par défaut.
L'environnement permet également aux remplacements administrateurs. L'utilisation du :static et :template pour environment-module-directory vous donne le chemin d'accès pour stocker des fichiers qui devraient remplacer le modèle standard d'un module et les fichiers statiques. Les chemins dans les répertoires respectifs doivent correspondre à ceux des propres fichiers source du module. Notez que les fichiers statiques et modèles dont un module utilise réellement sont mis en cache au temps de chargement du module, et ne changera donc pas à moins que l'image LISP soit redémarrée, ou que les fichiers source du module soient rechargés.
check-environment environment-change , environment config defaulted-config environment-directory mconfig-pathname @static environment-module-directory mconfig-storage defaulted-mconfig mconfig environment-module-pathname template-file @template static-file
Parfois, les systèmes évoluent de manière incompatible en arrière. Dans ce cas, pour que les configurations existantes continuent de fonctionner avec la nouvelle version, la migration des données d'exécution est nécessaire. Radiance propose un système pour automatiser ce processus et permettre une mise à niveau en douceur.
La migration entre les versions doit se produire automatiquement pendant la séquence de démarrage de Radiance. En tant qu'administrateur ou auteur, vous ne devriez pas avoir besoin d'effectuer des étapes supplémentaires pour que les migrations se produisent. Cependant, en tant qu'auteur de module, vous devrez naturellement fournir le code pour effectuer les étapes de migration des données nécessaires pour votre module.
Pour qu'un module soit migratable, il doit être chargé par un système ASDF qui a une spécification de version. La version doit suivre le schéma de nombres pointillés standard, avec un hachage de version facultatif qui peut être ajouté à la fin. Vous pouvez ensuite définir des étapes de migration entre les versions individuelles en utilisant define-version-migration . Une fois défini, Radiance reprendra automatiquement les versions en béton et effectuera les migrations nécessaires en séquence pour atteindre la version cible actuelle. Pour plus d'informations sur la procédure précise et ce que vous pouvez faire, voir migrate et migrate-versions .
Voir last-known-system-version , migrate-versions , define-version-migration , ready-dependency-for-migration , versions ensure-dependencies-ready , migrer, migrate
Enfin, Radiance fournit une séquence standard de démarrage et d'arrêt qui devrait garantir que les choses sont correctement configurées et préparées, et ensuite bien nettoyée. Une grande partie de cette séquence est simplement de garantir que certains crochets sont appelés dans l'ordre approprié et aux moments appropriés.
Bien que vous puissiez démarrer un serveur manuellement en utilisant la fonction d'interface appropriée, vous ne devez pas vous attendre à ce que les applications s'exécutent correctement si vous le faites de cette façon. Beaucoup d'entre eux s'attendront à ce que certains crochets soient appelés pour fonctionner correctement. C'est pourquoi vous devriez toujours, à moins que vous ne sachiez exactement ce que vous faites, utilisez startup et shutdown pour gérer une instance Radiance. La documentation des deux fonctions devrait expliquer exactement quels crochets sont déclenchés et dans quel ordre. Une implémentation peut fournir des définitions supplémentaires et non spécifiées sur les symboles dans le package d'interface, tant que lesdits symboles ne sont pas exportés.
Voir *startup-time* , uptime , server-start , server-ready server-stop server-shutdown , startup , startup-done , shutdown , shutdown-done , started-p
Ces interfaces sont distribuées avec radiance et font partie du package central. Les bibliothèques peuvent cependant prévoir des interfaces supplémentaires. Pour les implémentations des interfaces standard, les relaxations suivantes des contraintes de définition d'interface sont autorisées:
Les listes lambda qui contiennent &key peuvent être étendus par des arguments de mots clés dépendants de l'implémentation. Lambda-lists contenant &optional , mais non &key ou &rest peut être prolongée par d'autres arguments facultatifs. Les listes de lambda qui contiennent uniquement les arguments requises peuvent être étendues par d'autres arguments facultatifs ou de mots clés.
Cette interface prévoit une page d'administration. Il doit être utilisé pour tout type de paramètres configurables par l'utilisateur ou l'affichage d'informations système. Notez qu'en dépit d'être appelée «administration», ce n'est pas destiné uniquement aux administrateurs du système. La page doit être utilisable pour tout utilisateur.
La page d'administration doit être en mesure d'afficher un menu catégorisé et un panneau. Les panneaux sont fournis par d'autres modules et peuvent être ajoutés via admin:define-panel . Les panneaux qui donnent accès aux opérations sensibles doivent être correctement restreintes via l'option :access et une autorisation non défaut. Voir l'interface utilisateur pour une explication sur les autorisations.
Afin de créer un lien vers la page d'administration ou un panneau spécifique, utilisez le type de ressource page .
Voir admin:list-panels , admin:remove-panel , admin:define-panel , admin:panel
L'interface d'authentification est chargée de lier un utilisateur à une demande. Pour cette raison, il doit fournir une manière par laquelle un utilisateur peut s'authentifier contre le système. Comment cela se fait exactement est à la hauteur de l'implémentation. L'implémentation doit cependant fournir une page sur laquelle le processus d'authentification est initié. 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: