Radiance es un entorno de aplicación web, que es como un marco web, pero más general, más flexible. Debería permitirle escribir sitios web personales y generalmente aplicaciones desplegables fácilmente y de tal manera que puedan usarse en prácticamente cualquier configuración sin tener que someterse a adaptaciones especiales.
Radiance y módulos y aplicaciones asociados se distribuyen mediante rápida en un Dist separado. Para instalar Radiance, haz:
(ql-dist:install-dist "http://dist.shirakumo.org/shirakumo.txt")
(ql:quickload :radiance)
De ahí en adelante, debería poder cargar y usar cualquier tipo de módulo de radiación como Purplish directamente a través de QuickLisp's quickload .
Puede encontrar un tutorial que presente Radiance y la mayoría de los conceptos importantes, y explora cómo escribir una aplicación web en general, aquí. Debería darle una buena sensación sobre cómo hacer las cosas y darle consejos sobre dónde buscar si necesita una característica particular. En la última parte, también entrará en la configuración real y la implementación de una instalación de radiación en un servidor de producción.
Lo más básico que probablemente desea hacer es servir algún tipo de HTML. Así que trabajemos hacia eso y lo extendamos gradualmente. Antes de que podamos comenzar, necesitamos iniciar Radiance.
( ql :quickload :radiance )
( radiance :startup) Si esta es la primera vez que configura Radiance, obtendrá una nota al usar el módulo r-welcome . También debe darle un enlace que pueda abrir en su navegador para ver una pequeña página de saludo. Por ahora solo queremos colocar nuestra propia pequeña página junto a ella.
( in-package :rad-user )
(define-page example " /example " ()
( setf (content-type *response* ) " text/plain " )
" Hi! " )Visitar Localhost: 8080/Ejemplo ahora debe mostrar "Hola". Bastante aburrido. Así que escupiemos un poco de HTML. Por ahora, usaremos CL-Who, ya que es muy simple. Primero descargue rápido, luego ejecute lo siguiente:
(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. " )))))))Recompilar y actualizar más tarde y tenemos un estilo de fuente. A continuación, probablemente queremos agregarle un archivo CSS para peinarlo correctamente. También podríamos servir al CSS usando otra página, pero esa no es la mejor manera de hacer las cosas a largo plazo.
En su lugar, veamos cómo crear un módulo, que nos permitirá organizar las cosas de manera más ordenada. Puede crear los archivos para un módulo manualmente, pero por ahora nos conformaremos con un esqueleto generado automáticamente que Radiance puede proporcionarle.
(create-module " example " ) Debería devolverle una ruta en la que reside el módulo. Debe contener un sistema ASDF, un archivo LISP principal y dos carpetas, static y template . Sorprendentemente, la carpeta static es donde van los archivos servidos estáticamente, y template es para documentos de plantilla, si utiliza un sistema de plantilla.
Abramos el example.lisp y lleve nuestra página de ejemplo.
(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. " ))))))) Las páginas se identifican por un símbolo de nombre. Dado que ahora tenemos nuestro propio módulo y, por lo tanto, nuestro propio paquete, el símbolo de ejemplo anterior no será el mismo que el que hemos usado antes. Solo tendremos que eliminar la página en el paquete rad-user para evitar el choque.
(remove-page ' rad-user::example) Asegúrese de cargar el archivo de ejemplo siempre que lo cambie ahora para que los cambios entren en vigencia. A continuación, creemos un archivo CSS simple para arreglar un poco las cosas. El archivo será example.css colocado en la carpeta static . Aquí hay un CSS de muestra si no desea escribir el suyo.
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 ;
}A continuación, debemos modificar nuestro HTML para vincular realmente a la hoja de estilo. Para obtener la dirección a la hoja de estilo, tendremos que utilizar el sistema de enrutamiento de Radiance. Sin embargo, no se preocupe, no es una gran molestia.
(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. " ))))))) Actualice la página y, voilà, ahora también tiene algo de pizzazz. Probablemente querrá una explicación para todo el negocio uri-to-url . Explicarlo en su totalidad es manejado por las secciones que siguen a esta, pero la esencia es que asegura que el enlace al archivo estático se resuelva correctamente en cualquier configuración.
Uno de los conceptos más centrales en Radiance es el de un URI. Un URI es un objeto que consiste en una lista de dominios, un número de puerto opcional y una ruta (ver uri ). Es esencialmente una versión despojada de un URI general, y como tal no incluye un esquema, consulta o parte de fragmentos. Otra diferencia importante es que los domains URI se usan en varios puntos a lo largo del marco, tanto para capturar ubicaciones como para manejar la coincidencia de despacho.
Tenga en cuenta que los URI son mutables. Esto es importante para el rendimiento, ya que las modificaciones de URI tienen que suceder en varias partes que se encuentran en el camino crítico. Sin embargo, en el caso habitual no se espera que los URI se modifiquen fuera de algunas funciones selectas. La modificación de las partes de un URI de manera inesperada puede conducir a un comportamiento extraño.
Los URI tienen una representación de cadena única y se pueden serializar para una cadena y volver a un objeto URI completo nuevamente. URIS también se puede ver a los archivos FASL como literales, por lo que emitirlos de Macros está bien. La sintaxis para un URI es la siguiente:
URI ::= DOMAINS? (':' PORT)? '/' PATH?
DOMAINS ::= DOMAIN ('.' DOMAIN)*
DOMAIN ::= ('a'..'Z' | '0'..'9' | '-')
PORT ::= ('0'..'9'){1, 5}
PATH ::= .*
Puede usar uri-to-url para convertir un URI en una URL de concreto. La inversión, la codificación y el formato adecuado de todas las piezas se manejan automáticamente allí.
Vea 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 .
Para encapsular los datos que se envían hacia y desde, tenemos la idea de un objeto de solicitud ( request ) y respuesta ( response ). El objeto de solicitud contiene el URI que representa en qué ubicación va la solicitud, y todos los datos contenidos en la carga útil HTTP como Post, Get, Header y Cookie Variables. El objeto de respuesta contiene el código de retorno, los encabezados, las cookies y los datos del cuerpo reales.
Durante el procesamiento de una solicitud, estos dos objetos siempre deben estar presentes y vinculados a las variables *request* y *response* . Encapsulan mucha información muy vital que es necesaria para generar páginas dinámicas. Además, la solicitud contiene una tabla data opacas en la que puede almacenar datos arbitrarios. Esto es útil cuando necesita intercambiar información entre partes individuales del sistema que se puede alcanzar durante la ejecución de la solicitud.
Las solicitudes no necesariamente tienen que provenir del servidor HTTP. Para probar cosas, también puede construir una solicitud usted mismo y enviarla mediante programación. Cualquiera sea el caso, la interfaz principal para enviar una solicitud se llama request . Esto construirá un objeto de solicitud y respuesta para usted y manejará adecuadamente el URI. Si desea hacerlo usted mismo y realmente simplemente envíe un objeto de solicitud completo, puede usar execute-request .
Para el manejo real de una solicitud, ver despachadores, páginas y puntos finales de API.
Consulte *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 , datos, data return-code content-type external-format , conformidad, headers , encabezados, encabezados, nombre, nombre de cookies , name , nombre, value de rutas, cookie , nombre de ruta, 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
Antes de que se pueda enviar una solicitud, pasa por algo llamado sistema de enrutamiento. A diferencia de otros marcos, donde las 'rutas' designan lo que maneja una solicitud, en Radiance, una ruta ( route ) es una forma de traductor URI. Esta parte del sistema es lo que es responsable de crear y defender dos "universos", uno interno y externo.
El universo interno es en la que vive las aplicaciones web reales. El universo externo es el que vive el servidor HTTP y que vive un usuario del sitio web. Esta distinción es necesaria para permitirle, una mano, escribir aplicaciones web sin tener que preocuparse por cómo podría verse una configuración potencial en un servidor en algún momento. No tiene que preocuparse por qué tipo de configuración de dominio, puerto y ruta puede ser necesaria para ejecutar su aplicación. Por otro lado, le permite, como webadmin, personalizar y ejecutar el sistema a sus deseos exactos sin temor a romper cosas.
Todo esto es facilitado por rutas, de las cuales hay dos tipos: mapeo y rutas de reversión. Las rutas de mapeo son responsables de convertir un URI del universo externo en uno del universo interno. Por lo general, esto implica cortar el dominio de nivel superior y tal vez hacer un mapeo de subdominios. Las rutas de inversión hacen lo contrario: van del universo interno al externo. Esto es necesario para hacer que los enlaces en sus páginas servidas se refieran a los recursos que realmente son accesibles desde el exterior. Por lo general, esto implica revertir el mapeo del subdominio y agregar nuevamente el dominio de nivel superior.
Las rutas pueden realizar un trabajo arbitrario. En el nivel más básico, son simplemente funciones que modifican un URI de alguna manera. Esto le permite crear un sistema muy flexible que debería ser lo suficientemente potente como para acomodar todas sus necesidades como administrador. Como escritor de aplicaciones, solo debe asegurarse de usar external-uri o uri-to-url en todos los enlaces que pone en sus páginas.
Consulte 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
Finalmente llegamos a la parte que realmente genera contenido para una solicitud. Los despachadores de URI son una subclase de URI que también lleva un nombre, una función y una prioridad. Live en una lista de prioridad, que se procesa cada vez que llega una solicitud. El URI de la solicitud se combina con cada despachador. Luego se ejecuta la función del primer despachador que coincide.
Y eso es todo. La función del despachador es responsable de establecer los valores necesarios en el objeto de respuesta para entregar el contenido de la página. Para hacer esto, puede establecer directamente el campo data del objeto de respuesta, o puede devolver un valor apropiado de la función. Radiance solo acepta cuatro tipos de valores: stream , pathname , nombre, string y (array (unsigned-byte 8)) .
Si un despachador de URI no tiene un número de prioridad explícita, su prioridad sobre otros está determinada por la especificidad del URI. Consulte la función de clasificación de URI uri> para una explicación sobre cómo se calcula exactamente esto.
Consulte uri-dispatcher , name , dispatch-function , priority , uri-dispatcher , remove-uri-dispatcher , list-uri-dispatchers , uri-dispatcher> , define-uri-dispatcher , dispatch
Las páginas son lo que probablemente usará para definir sus funciones de servicio reales de contenido. Sin embargo, una página es solo un URI-Dispatcher con alguna funcionalidad adicional en la macro de definición que le facilita las cosas. Lo más notable son las opciones extensibles, para las cuales puede encontrar una explicación a continuación.
Hay un par de páginas predeterminadas configuradas por Radiance. Primero están las páginas de favicon y robots , que simplemente sirven los archivos respectivos de la static/ directorio de Radiance. Probablemente desee proporcionar sus propias páginas para eso o actualizar los archivos en su servidor de producción.
Luego está la página static , que es responsable de servir contenido estático para todas las aplicaciones y módulos web. Debe estar activo en cualquier dominio y siempre en la ruta /static/... donde ... debe tener un formulario donde el primer directorio sea el nombre de un módulo, y el resto es una ruta dentro de static/ directorio de ese módulo. Esto le permite poder consultar siempre archivos estáticos como CSS, JS e imágenes a través de una ruta común.
Finalmente, está la página api , que es responsable de manejar el envío de puntos finales de API, que se explican en la siguiente sección. La página actúa de manera similar a la estática capturando la ruta /api/... en todos los dominios.
Consulte page , remove-page , define-page
Radiance proporciona soporte integrado para la definición de API REST. Esta no es solo una característica táctica, sino porque la mayoría de las aplicaciones modernas quieren proporcionar una API de algún tipo, y porque Radiance aconseja una cierta forma de escribir sus aplicaciones que necesariamente involucran puntos finales de API.
Conceptualmente, los puntos finales de API son funciones que se pueden llamar a través de una solicitud del navegador. Luego, su respuesta se serializa a un formato que es legible por el solicitante, sea lo que sea. Sin embargo, es importante recordar que los puntos finales de la API deben ser utilizables tanto por usuarios como para programas. Radiance fomenta esto porque, por lo general, el usuario también tendrá que realizar cualquier tipo de acción que se pueda realizar programáticamente a través de una API de alguna manera. Para evitar la duplicación, los dos se pueden combinar.
Como tal, generalmente se debe proporcionar cualquier tipo de acción de modificación de datos a través de un punto final API que reaccione de manera ligeramente diferente dependiendo de si un usuario o una aplicación lo solicita. En el caso de un usuario, generalmente debe redirigir a una página apropiada, y en el caso de una aplicación debe proporcionar una carga útil de datos en un formato legible.
La primera parte de todo esto es el sistema de formato API, que es responsable de serializar datos a algún formato especificado. Por defecto, solo se suministra un formato basado en la expresión S, pero se puede cargar fácilmente un contribuyente para obtener la salida JSON.
La segunda parte es la especificación del parámetro Post/GET browser . Si ese parámetro contiene la cadena exacta "true" , entonces la solicitud API se trata como proveniente de un usuario y, por lo tanto, se debe obtener una redirección en lugar de una carga útil de datos.
Su aplicación debe hacer uso de esas cosas para proporcionar una API integrada adecuadamente. Ahora, una definición de punto final real está compuesta de un nombre, una función sin procesar, una lista lambda que describe los argumentos de la función y una función de análisis de solicitud. Por lo general, para sus argumentos, solo los argumentos requeridos y opcionales tienen sentido. Después de todo, una solicitud HTTP solo tiene "argumentos de palabras clave" que puede proporcionar, y esos pueden estar presentes o faltantes.
El nombre de un punto final API también sirve como el identificador que le indica dónde puede alcanzarlo. Los puntos finales de API viven en la ruta /api/ , seguido del nombre del punto final. Como tal, usted es responsable de prefijo sus puntos finales con el nombre de su módulo o aplicación para evitar tropezar accidentalmente en otros puntos finales. Esto es diferente a los despachadores de URI, porque los puntos finales de la API tienen que coincidir exactamente y no permiten ninguna ambigüedad o procesamiento de la ruta. Por lo tanto, cada punto final debe tener una ruta única, que también puede servir inmediatamente como nombre.
La función sin procesar es la función para la que la API proporciona una interfaz. Es responsable de realizar la acción solicitada y devolver los datos apropiados como se describió anteriormente. Para devolver datos de API formateados, consulte api-output . Para redirigir en el caso de una solicitud de navegador, consulte redirect .
Finalmente, la función de análisis de solicitudes es responsable de tomar un objeto de solicitud, extraer los argumentos que la función necesita y finalmente llamar a esa función con los argumentos apropiados, si es posible. La función de análisis puede indicar un error api-argument-missing si falta un argumento requerido. Los argumentos superfluos deben ser ignorados.
También puede llamar programáticamente un punto final de la API usando call-api , o simular una llamada de solicitud con call-api-request , sin tener que pasar por todo el mecanismo de despacho de URI.
De manera similar a las páginas, las definiciones de punto final de API también aceptan opciones extensibles que simplifican la definición. Consulte la siguiente sección para obtener una explicación de las opciones.
Ver api , *default-api-format* , *serialize-fallback* , api-format , remove-api-format , list-api-formats , define-api-format , api-output , api-serialize , api-endpoint , list-api-endpoints remove-api-endpoint api-endpoint , name , handler , argslist , call-api-request request-handler call-api . define-api
Las opciones son una forma de proporcionar una macro de definición extensible. Esto es útil cuando un marco proporciona una forma común de definir algo, pero otras partes pueden querer proporcionar extensiones a eso para acortar las operaciones comunes. Por ejemplo, una tarea común es restringir un punto final de página o API a las personas que tienen las credenciales de acceso requeridas.
Para facilitar esto, Radiance proporciona un mecanismo de opciones bastante genérico. Las opciones se dividen por un tipo de opción que designa a qué macro de definición pertenece la opción. Radiance proporciona los tipos de opciones api y page fuera del cuadro.
Cada opción tiene una palabra clave para un nombre y una función de expansor que debe aceptar una serie de argumentos, dependiendo del tipo de opción. Siempre proporcionados como argumentos son el nombre de la cosa que se está definiendo, la lista de formas corporales de la definición y un valor final, opcional y opcional que se proporcionó a la opción en la lista de opciones, si se mencionó en absoluto. Esta función de expansión es responsable de transformar las formas del cuerpo de la macro de definición de alguna manera. También puede emitir una segunda forma que se coloca fuera de la definición misma, para permitir la configuración del entorno de alguna manera.
Consulte option , option-type , name , expander , option , remove-option , list-options , define-option , expand-options
El concepto de un módulo es esencial para el resplandor. Sirve como la representación de una "parte" del todo. En un nivel técnico, un módulo es un paquete que tiene metadatos especiales adjuntos. Es proporcionado por el sistema modularize y se utiliza para facilitar los ganchos y desencadenantes, las interfaces y el seguimiento de algunas otras piezas de información.
Lo que esto significa para usted es que, en lugar de un defpackage estándar, debe usar un formulario define-module para definir su paquete principal. La sintaxis es la misma que defpackage , pero incluye algunas opciones adicionales como :domain , que le permite especificar el dominio principal en el que este módulo debe funcionar (si corresponde).
El sistema de módulos también permite el atado de un sistema ASDF a un módulo. Si eso se hace, entonces el sistema ASDF se convierte en un "módulo virtual". Para hacer esto, debe agregar tres opciones a la definición de su sistema:
:defsystem-depends-on (:radiance)
:class "radiance:virtual-module"
:module-name "MY-MODULE"
Esto permite que Radiance identifique y asocie la información del sistema ASDF en su módulo. Para la creación automatizada de las definiciones necesarias del sistema y el módulo para un nuevo módulo, consulte 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
Uno de los mecanismos que proporciona Radiance para permitir módulos de integración entre sí son los ganchos. Los ganchos le permiten ejecutar una función arbitraria en respuesta a algún tipo de evento. Por ejemplo, un software de foro podría configurar un gancho que se active cada vez que se crea una nueva publicación. Una extensión podría definir un desencadenante en ese gancho que realiza tareas adicionales.
Un gancho puede tener un número arbitrario de desencadenantes definidos en él, pero debe asegurarse de que un gatillo no tarda demasiado, ya que activar un gancho es una operación de bloqueo que no terminará hasta que todos los desencadenantes se hayan completado. Como tal, una operación de activación de larga duración podría retrasar una respuesta de solicitud durante demasiado tiempo.
A veces, los ganchos deberían funcionar más como interruptores, donde pueden estar "encendidos" durante mucho tiempo, hasta que se apague "apagados" nuevamente más tarde. Si se definen nuevos desencadenantes durante ese tiempo, deben llamarse automáticamente. Esto es lo que facilita el define-hook-switch . Produce dos ganchos. Una vez que se ha activado el primero, cualquier disparador que se define más tarde se llama automáticamente hasta que se active el segundo gancho. Esto permite que los disparadores en ganchos como server-start funcionen correctamente, incluso si el disparador solo se define después de que el servidor ya se ha iniciado.
Consulte list-hooks , define-hook , remove-hook , define-trigger , remove-trigger , trigger , define-hook-switch
Para evitar convertirse en monolítico y para permitir backends extensibles, Radiance incluye un sistema de interfaz. En el sentido más general, una interfaz proporciona una promesa sobre cómo algunas funciones, macros, variables, etc. deberían funcionar, pero en realidad no las implementa. La funcionalidad real que hace que todo lo que funcione la interfaz se presenta a una implementación. Esto permite a los usuarios codificar una interfaz y hacer uso de su funcionalidad proporcionada, sin vincularse a ningún backend particular.
Para un ejemplo concreto, supongamos que hay una interfaz para una base de datos. Esto es sensato, ya que hay muchos tipos diferentes de bases de datos, que ofrecen muchas formas diferentes de interacción, pero aún así también ofrecen algunas operaciones muy comunes: almacenar datos, recuperar datos y modificar los datos. Por lo tanto, creamos una interfaz que ofrece estas operaciones comunes. Entonces depende de una implementación para un tipo específico de base de datos para que las operaciones reales funcionen. Como escritor de aplicaciones, puede utilizar la interfaz de la base de datos y, con ella, hacer que su aplicación funcione automáticamente con muchas bases de datos diferentes.
Además de dar una ventaja a los escritores de aplicaciones, el desacoplamiento que proporcionan las interfaces también significa que un administrador del sistema puede escribir su propia implementación con relativa facilidad, en caso de que sus requisitos particulares no cumplan con las implementaciones existentes. Gracias a la optica de las interfaces, una implementación puede proporcionar un puente a algo que se ejecuta en el proceso LISP y algo que es completamente externo. Esto deja muchas opciones abiertas para el administrador de un sistema de producción para permitirles elegir exactamente lo que necesitan.
En la práctica, las interfaces son tipos especiales de módulos y, por lo tanto, tipos especiales de paquetes. Como parte de su definición, incluyen una serie de definiciones para otros enlaces, como funciones, variables, etc., ya que es un paquete, como usuario puede usar los componentes de la interfaz como si usara cualquier otra cosa en cualquier otro paquete. No hay diferencia. Como escritor de implementación, simplemente redefinir todas las definiciones que la interfaz describe.
Para cargar realmente un módulo que utilice una interfaz, una implementación para la interfaz debe cargarse de antemano. De lo contrario, las macros no podrían funcionar correctamente. Por lo tanto, para permitir dependiendo de las interfaces en la definición de su sistema ASDF sin tener que consultar una implementación específica, Radiance proporciona una extensión ASDF. Esta extensión permite agregar una lista como (:interface :foo) a su :depends-on List. Radiance luego resolverá la interfaz a una implementación concreta de la misma cuando se carga el módulo.
Radiance proporciona un montón de interfaces estándar. Cada una de esas interfaces tiene al menos una implementación estándar proporcionada por Radiance-Contribs. Las interfaces son:
adminauthbancachedatabaseloggermailprofilerateserversessionuserLas interfaces se describen en profundidad a continuación.
Consulte interface , interface-p , implementation , implements , reset-interface , define-interface-extension , find-implementation , load-implementation , define-interface , define-implement-trigger
Para permitir la ejecución de múltiples instancias de radiancia con diferentes configuraciones en la misma máquina, Radiance proporciona lo que llama un sistema de entorno. El entorno es básicamente el conjunto de archivos de configuración y tiempo de ejecución para Radiance en sí y todos los módulos cargados. La configuración de Radiance también incluye la asignación de la interfaz para la implementación elegida y, por lo tanto, decide qué debe elegir si se solicita una interfaz.
El entorno particular que se utiliza se elige a más tardar cuando se llama startup , y el más temprano cuando se carga un módulo. En el último caso, se proporcionan reinicios interactivos para permitirle elegir un entorno. Esto es necesario, ya que de lo contrario, Radiance no podrá resolver el mapeo de la interfaz.
Como parte del sistema de entorno, Radiance le proporciona un sistema de configuración que puede, y probablemente debería, usar para su aplicación. Asegura que la configuración se multiplexe correctamente para cada entorno y que la configuración siempre sea persistente. También utiliza un formato de almacenamiento legible por humanos, de modo que los archivos se pueden leer y modificar sin requerir ninguna herramienta especial.
Consulte ubicuo para el manejo real y las instrucciones de uso del almacenamiento de configuración. Solo tenga en cuenta que en lugar de las funciones value , Radiance proporciona funciones config .
Además de los archivos de configuración, el entorno también proporciona ubicaciones de almacenamiento consistentes para archivos de datos de tiempo de ejecución, como cargas de usuarios, archivos de caché, etc. Puede recuperar esta ubicación utilizando environment-module-directory y environment-module-pathname . Al almacenar cargas y cachés, un módulo debe utilizar estas rutas para presentar una interfaz consistente al administrador.
En un sistema implementado, se puede desear cambiar la ubicación de las rutas de almacenamiento del entorno, en cuyo caso se alienta al administrador a suministrar nuevos métodos sobre environment-directory y environment-module-directory para personalizar el comportamiento como se desea. Consulte también las cadenas de documentación asociadas para obtener más detalles y acciones predeterminadas.
El entorno también permite las anulaciones de administrador. Uso de :static y :template para environment-module-directory le brindan la ruta para almacenar archivos que deberían anular la plantilla estándar y los archivos estáticos de un módulo. Las rutas dentro de los directorios respectivos deben coincidir con las de los propios archivos de origen del módulo. Tenga en cuenta que los archivos estáticos y de plantilla que un módulo realmente utiliza se almacenan en caché al tiempo de carga del módulo y, por lo tanto, no cambiarán a menos que se reinicie la imagen LISP, o los archivos de origen del módulo se vuelven a cargar.
Consulte 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 @static @static @static @static @static, @static
A veces los sistemas evolucionan de manera incompatible hacia atrás. En ese caso, para que las configuraciones existentes continúen funcionando con la nueva versión, la migración de datos de tiempo de ejecución es necesaria. Radiance ofrece un sistema para automatizar este proceso y permitir una actualización suave.
La migración entre versiones debe ocurrir automáticamente durante la secuencia de inicio de Radiance. Como administrador o autor, no debe necesitar realizar pasos adicionales para que ocurran migraciones. Sin embargo, como autor del módulo, naturalmente tendrá que proporcionar el código para realizar los pasos de migración de datos necesarios para su módulo.
Para que un módulo sea migratable, debe ser cargado por un sistema ASDF que tenga una especificación de versión. La versión debe seguir el esquema estándar de números punteados, con un hash de versión opcional que se puede agregar al final. Luego puede definir los pasos de migración entre las versiones individuales mediante el uso de define-version-migration . Una vez definido, Radiance recogerá automáticamente las versiones concretas y realizará las migraciones necesarias en secuencia para alcanzar la versión objetivo actual. Para obtener más información sobre el procedimiento preciso y lo que puede hacer, consulte migrate-versions migrate y migradas.
Ver last-known-system-version , migrate-versions , define-version-migration , ready-dependency-for-migration , versions ensure-dependencies-ready , migrar, migrate
Finalmente, Radiance proporciona una secuencia estándar de inicio y apagado que debería garantizar que las cosas estén configuradas y preparadas correctamente, y luego se limpian muy bien. Una gran parte de esa secuencia es solo garantizar que ciertos ganchos se llamen en el orden adecuado y en los momentos apropiados.
Si bien puede iniciar un servidor manualmente utilizando la función de interfaz apropiada, no debe esperar que las aplicaciones se ejecuten correctamente si lo hace de esa manera. Muchos de ellos esperarán que se llamen ciertos ganchos para que funcionen correctamente. Es por eso que siempre debe, a menos que sepa exactamente lo que está haciendo, use startup y shutdown para administrar una instancia de Radiance. La documentación de las dos funciones debe explicar exactamente qué ganchos se activan y en qué orden. Una implementación puede proporcionar definiciones adicionales y no especificadas sobre símbolos en el paquete de interfaz, siempre que no se exporten dichos símbolos.
Consulte *startup-time* , uptime server-start , server-ready server-stop server-shutdown , startup , inicio startup-done , shutdown , shutdown-done , started-p
Estas interfaces se distribuyen con Radiance y son parte del paquete central. Sin embargo, las bibliotecas pueden proporcionar interfaces adicionales. Para las implementaciones de interfaces estándar, se permiten las siguientes relajaciones de las restricciones de definición de interfaz:
Las listas Lambda que contienen &key pueden extenderse por argumentos de palabras clave más dependientes de la implementación. Las listas de lambda que contienen &optional , pero no &key o &rest pueden extenderse mediante argumentos opcionales adicionales. Las listas LAMBDA que contienen solo argumentos requeridos pueden extenderse por otros argumentos opcionales o de palabras clave.
Esta interfaz proporciona una página de administración. Debe usarse para cualquier tipo de configuración configurable por el usuario o pantalla de información del sistema. Tenga en cuenta que a pesar de ser llamado "administración", esto no pretende únicamente para los administradores del sistema. La página debe ser utilizable para cualquier usuario.
Se requiere la página de administración para poder mostrar un menú categorizado y un panel. Los paneles son proporcionados por otros módulos y se pueden agregar a través de admin:define-panel . Los paneles que dan acceso a operaciones confidenciales deben restringirse adecuadamente a través de la opción :access y un permiso no defapricado. Consulte la interfaz de usuario para obtener una explicación sobre los permisos.
Para vincular a la página de administración, o un panel específico sobre ella, use el tipo de recurso page .
Consulte admin:list-panels , admin:remove-panel , admin:define-panel , admin:panel
La interfaz de autenticación es responsable de vincular a un usuario a una solicitud. Por esta razón, debe proporcionar alguna manera por la cual un usuario pueda autenticarse contra el sistema. La forma en que esto se hace exactamente depende de la implementación. Sin embargo, la implementación debe proporcionar una página en la que se inicie el proceso de autenticación. 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: