O Radiance é um ambiente de aplicativos da web, que é como uma estrutura da web, mas mais geral, mais flexível. Deve permitir que você escreva sites pessoais e geralmente implantam aplicativos com facilidade e de tal maneira que possam ser usados praticamente qualquer configuração sem precisar passar por adaptações especiais.
O brilho e os módulos e aplicações associados são distribuídos via Quicklisp em um Dist separado. Para instalar o Radiance, faça:
(ql-dist:install-dist "http://dist.shirakumo.org/shirakumo.txt")
(ql:quickload :radiance)
A partir daí, você poderá carregar e usar qualquer tipo de módulo de radiação como Purplish diretamente via quickload da Quicklisp.
Você pode encontrar um tutorial que apresente brilho e a maioria dos conceitos importantes e explora como escrever um aplicativo da Web em geral, aqui. Deve dar uma boa sensação de como fazer as coisas e dar a você dicas sobre onde procurar se você precisar de um recurso específico. Na última parte, também entrará na configuração e implantação reais de uma instalação de brilho em um servidor de produção.
A coisa mais básica que você provavelmente deseja fazer é servir algum tipo de HTML. Então, vamos trabalhar para isso e estendê -lo gradualmente. Antes de começarmos, precisamos iniciar o brilho.
( ql :quickload :radiance )
( radiance :startup) Se esta é a primeira vez que configurará o Radiance, você receberá uma nota sobre isso usando o módulo r-welcome . Também deve fornecer um link que você pode abrir no seu navegador para ver uma pequena página de saudação. Por enquanto, apenas queremos colocar nossa própria pequena página ao lado dela.
( in-package :rad-user )
(define-page example " /example " ()
( setf (content-type *response* ) " text/plain " )
" Hi! " )Visitando localhost: 8080/exemplo agora deve apenas mostrar "oi". Bastante chato de fato. Então, vamos cuspir um pouco de HTML. Por enquanto, usaremos CL-Who, pois é muito simples. Primeiro o rápido e depois execute o seguinte:
(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. " )))))))Um recompile e uma atualização mais tarde e temos algum estilo de fonte acontecendo. Em seguida, provavelmente queremos adicionar um arquivo CSS para estilizá -lo corretamente. Poderíamos servir o CSS usando outra página também, mas essa não é a melhor maneira de fazer as coisas a longo prazo.
Em vez disso, vamos ver como criar um módulo, que nos permitirá organizar as coisas de uma maneira mais ordenada. Você pode criar os arquivos para um módulo manualmente, mas por enquanto resolveremos um esqueleto gerado automaticamente com o qual o Radiance pode fornecer.
(create-module " example " ) Deve retornar um caminho no qual o módulo reside. Ele deve conter um sistema ASDF, um arquivo LISP principal e duas pastas, static e template . Surpreendentemente, a pasta static é onde os arquivos servidos estaticamente vão e template é para documentos de modelo, se você usar um sistema de modelo.
Vamos abrir o example.lisp e carregar nossa página de exemplo dela.
(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. " ))))))) As páginas são identificadas por um símbolo de nome. Como agora temos nosso próprio módulo e, portanto, nosso próprio pacote, o símbolo de exemplo acima não será o mesmo que o que usamos antes. Apenas precisamos remover a página no pacote rad-user para evitar o confronto.
(remove-page ' rad-user::example) Certifique -se de carregar o arquivo de exemplo sempre que alterá -lo agora para que as alterações tenham efeito. Em seguida, vamos criar um arquivo CSS simples para enfeitar um pouco as coisas. O arquivo será example.css colocado na pasta static . Aqui está uma amostra CSS se você não quiser escrever o seu.
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 ;
}Em seguida, precisamos modificar nosso HTML para realmente vincular a folha de estilo. Para obter o endereço na folha de estilo, teremos que usar o sistema de roteamento da Radiance. Não se preocupe, porém, não é muito aborrecido.
(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. " ))))))) Atualize a página e voilà agora também tem um pouco de pizzazz. Você provavelmente deseja uma explicação para todo o negócio uri-to-url . Explicando -o integralmente é tratado pelas seções seguintes, mas a essência é que garante que o link para o arquivo estático seja resolvido corretamente sob qualquer configuração.
Um dos conceitos mais centrais em brilho é o de um URI. Um URI é um objeto que consiste em uma lista de domínios, um número de porta opcional e um caminho (consulte uri ). É essencialmente uma versão despojada de um URI geral e, como tal, não inclui um esquema, consulta ou parte do fragmento. Outra diferença importante é que os URIs domains são usados em vários pontos ao longo da estrutura, tanto para capturar locais quanto para lidar com a correspondência de expedição.
Observe que os URIs são mutáveis. Isso é importante para o desempenho, pois as modificações do URI precisam acontecer em várias partes que estão no caminho crítico. No entanto, no caso usual, não se espera que os URIs sejam modificados fora de algumas funções selecionadas. Modificar as partes de um URI de maneiras inesperadas pode levar a um comportamento estranho.
Os URIs têm uma representação exclusiva de string e podem ser serializados para cordas e analisados novamente em um objeto URI completo novamente. Os URIs também podem ser despejados nos arquivos FASL como literais, portanto, emitá -los de macros é bom. A sintaxe para um URI é a seguinte:
URI ::= DOMAINS? (':' PORT)? '/' PATH?
DOMAINS ::= DOMAIN ('.' DOMAIN)*
DOMAIN ::= ('a'..'Z' | '0'..'9' | '-')
PORT ::= ('0'..'9'){1, 5}
PATH ::= .*
Você pode usar uri-to-url para transformar um URI em um URL de concreto. A reversão, a codificação e a formatação adequada de todas as peças é tratada para você automaticamente lá.
Veja 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 os dados enviados para e a partir de, temos a idéia de um objeto de solicitação ( request ) e resposta ( response ). O objeto de solicitação mantém o URI que representa para qual local a solicitação vai e todos os dados contidos na carga útil HTTP, como postagem, obtenção, cabeçalho e variáveis de cookies. O objeto de resposta contém o código de retorno, cabeçalhos, cookies e dados do corpo reais.
Durante o processamento de uma solicitação, esses dois objetos devem estar sempre presentes e vinculados às variáveis *request* e *response* . Eles encapsulam muitas informações muito vitais necessárias para gerar páginas dinâmicas. Além disso, a solicitação contém uma tabela data opaca na qual você pode armazenar dados arbitrários. Isso é útil quando você precisa trocar informações entre partes individuais do sistema que podem ser alcançadas durante a execução da solicitação.
As solicitações não precisam necessariamente vir do servidor HTTP. Para testar as coisas, você também pode construir uma solicitação e enviá -la programaticamente. Seja qual for o caso, a interface principal para despachar uma solicitação é chamada de request . Isso construirá um objeto de solicitação e resposta para você e lidará adequadamente com o URI. Se você quiser fazer isso sozinho e realmente enviar um objeto de solicitação completo, você pode usar execute-request .
Para o manuseio real de uma solicitação, consulte Dispatchers, Páginas e pontos de extremidade da API.
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 render-error-page execute-request *debugger* set-data handle-condition request
Antes que uma solicitação possa ser despachada, ela passa por algo chamado sistema de roteamento. Ao contrário de outras estruturas, onde 'rotas' designam o que lida com uma solicitação, em Radiance uma rota ( route ) é uma forma de tradutor de URI. Esta parte do sistema é o que é responsável por criar e defender dois "universos", um interno e um externo.
O universo interno é o único aplicativo da Web real. O universo externo é aquele que o servidor HTTP e um usuário do site vive. Essa distinção é necessária para permitir que você, uma mão, escreva aplicativos da Web sem precisar se preocupar com a configuração potencial de um servidor em algum momento. Você não precisa se preocupar com que tipo de domínio, porta e configuração do caminho podem ser necessárias para executar seu aplicativo. Por outro lado, permite que você, como um webadmin, personalize e execute o sistema de acordo com seus desejos exatos, sem medo de quebrar as coisas.
Tudo isso é facilitado por rotas, das quais existem dois tipos: mapeamento e rotas de reversão. As rotas de mapeamento são responsáveis por transformar um URI do universo externo em um do universo interno. Geralmente, isso envolve cortar o domínio de nível superior e talvez fazer um mapeamento de subdomínios. As rotas de reversão fazem o oposto- elas vão do universo interno para o externo. Isso é necessário para fazer links em suas páginas servidas, referem -se a recursos que são realmente acessíveis de fora. Geralmente, isso envolve reverter o mapeamento de subdomínio e adicionar o domínio de nível superior novamente.
As rotas podem realizar um trabalho arbitrário. No nível mais básico, são apenas funções que modificam um URI de alguma maneira. Isso permite criar um sistema muito flexível que deve ser poderoso o suficiente para acomodar todas as suas necessidades como administrador. Como escritor de aplicativos, você só precisa usar external-uri ou uri-to-url em todos os links que você coloca em suas 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, chegamos à parte que realmente gera conteúdo para uma solicitação. Os despachantes de URI são uma subclasse de URI que também carrega um nome, uma função e uma prioridade. O LIVE em uma lista classificada prioritária, que é processada sempre que uma solicitação chegar. O URI da solicitação é comparado com cada despachante. A função do primeiro despachante que corresponde é executada.
E é isso. A função do despachante é responsável por definir os valores necessários no objeto de resposta para entregar o conteúdo da página. Para fazer isso, ele pode definir diretamente o campo data do objeto de resposta ou você pode retornar um valor apropriado da função. O Radiance aceita apenas quatro tipos de valores: stream , pathname , string e (array (unsigned-byte 8)) .
Se um despachante de URI não tiver um número de prioridade explícito, sua prioridade sobre outros será determinada pela especificidade do URI. Consulte a função de classificação URI uri> para uma explicação sobre como exatamente isso é calculado.
Consulte uri-dispatcher , name , dispatch-function , priority , uri-dispatcher , remove-uri-dispatcher , list-uri-dispatchers , uri-dispatcher> , define-uri-dispatcher , dispatch
As páginas são o que você provavelmente usará para definir suas funções reais de servir de conteúdo. No entanto, uma página é apenas um URI-Dispatcher com alguma funcionalidade extra na macro de definição que facilita as coisas. O mais notável são as opções extensíveis, para as quais você pode encontrar uma explicação abaixo.
Existem algumas páginas padrão estabelecidas pelo próprio Radiance. Primeiro, há as páginas favicon e robots , que simplesmente servem os respectivos arquivos do static/ diretório da Radiance. Você provavelmente deseja fornecer suas próprias páginas para isso ou atualizar os arquivos no seu servidor de produção.
Depois, há a página static , responsável por servir conteúdo estático para todos os aplicativos e módulos da Web. Deve estar ativo em qualquer domínio e sempre no caminho /static/... onde ... deve ter um formulário em que o primeiro diretório seja o nome de um módulo, e o restante é um caminho dentro do static/ diretório desse módulo. Isso permite que você sempre possa se referir a arquivos estáticos como CSS, JS e imagens através de um caminho comum.
Finalmente, há a página api , responsável por lidar com o despacho dos pontos de extremidade da API, que são explicados na seção a seguir. A página age de maneira semelhante à estática capturando o caminho /api/... em todos os domínios.
Consulte page , remove-page , define-page
O Radiance fornece suporte integrado para a definição da API REST. Este não é apenas um recurso de atendimento, mas porque a maioria dos aplicativos modernos deseja fornecer algum tipo de API e porque o Radiance aconselha uma certa maneira de escrever seus aplicativos que necessariamente envolvem pontos de extremidade da API.
Conceitualmente, os pontos de extremidade da API são funções que são chamáveis por meio de uma solicitação do navegador. Sua resposta é então serializada a um formato legível pelo solicitante, qualquer que seja. Importante lembrar, no entanto, é que os terminais da API devem ser utilizáveis por usuários e programas. A Radiance incentiva isso, porque geralmente qualquer tipo de ação que possa ser executada programaticamente através de uma API também deverá ser executada pelo usuário de alguma forma. Para evitar a duplicação, os dois podem ser conflitados.
Como tal, geralmente qualquer tipo de ação de modificação de dados deve ser fornecida através de um endpoint da API que reage um pouco diferente, dependendo de um usuário ou um aplicativo solicitar. No caso de um usuário, geralmente deve redirecionar de volta para uma página apropriada e, no caso de um aplicativo, deve fornecer uma carga útil de dados em um formato legível.
A primeira parte de tudo isso é o sistema de formato API, responsável por serializar dados para algum formato especificado. Por padrão, apenas um formato baseado em S-expressão S é fornecido, mas um contributo para obter a saída JSON pode ser facilmente carregado.
A segunda parte é a especificação do parâmetro POST/GET browser . Se esse parâmetro contiver a sequência exata "true" , a solicitação da API será tratada como proveniente de um usuário e, portanto, um redirecionamento em vez de uma carga útil de dados deve ser emitido.
Seu aplicativo deve usar essas coisas para fornecer uma API adequadamente integrada. Agora, uma definição real do ponto de extremidade é composta por um nome, uma função bruta, uma lista lambda descrevendo os argumentos da função e uma função de análise de solicitação. Normalmente, para seus argumentos, apenas argumentos necessários e opcionais fazem sentido. Afinal, uma solicitação HTTP possui apenas "argumentos de palavras -chave" que ela pode fornecer, e elas podem estar presentes ou ausentes.
O nome de um endpoint da API também serve como o identificador que informa onde você pode alcançá -lo. Os pontos de extremidade da API vivem no /api/ PATH, seguido pelo nome do endpoint. Como tal, você é responsável por prefixar seus pontos de extremidade com o nome do seu módulo ou aplicativo para evitar tropeçar acidentalmente em outros pontos de extremidade. Isso é diferente nos despachantes do URI, porque os terminais da API precisam corresponder exatamente e não permitir nenhuma ambiguidade ou processamento do caminho. Assim, todo terminal deve ter um caminho único, que também pode servir imediatamente como o nome.
A função bruta é a função para a qual a API fornece uma interface. É responsável por executar a ação solicitada e retornar os dados apropriados, conforme descrito acima. Para retornar dados da API formatada, consulte api-output . Para redirecionar no caso de uma solicitação do navegador, consulte redirect .
Finalmente, a função de análise de solicitação é responsável por tomar um objeto de solicitação, extrair os argumentos que a função precisa e, finalmente, chamando essa função com os argumentos apropriados- se possível. A função de análise pode sinalizar um erro api-argument-missing se um argumento necessário estiver ausente. Argumentos supérfluos devem ser ignorados.
Você também pode chamar programaticamente um endpoint da API usando call-api ou simular uma chamada de solicitação com call-api-request , sem ter que passar por todo o mecanismo de despacho de URI.
Da mesma forma que as páginas, as definições de endpoint da API também aceitam opções extensíveis que simplificam a definição. Consulte a seção a seguir para uma explicação de opções.
See api , *default-api-format* , *serialize-fallback* , api-format , remove-api-format , list-api-formats , define-api-format , api-output , api-serialize , api-endpoint , remove-api-endpoint , list-api-endpoints , api-endpoint , name , handler , argslist , request-handler , call-api-request , call-api , define-api
As opções são uma maneira de fornecer uma macro de definição extensível. Isso é útil quando uma estrutura fornece uma maneira comum de definir algo, mas outras peças podem querer fornecer extensões para isso para tornar as operações comuns mais curtas. Por exemplo, uma tarefa comum é restringir uma página ou endpoint da API às pessoas que possuem as credenciais de acesso necessárias.
Para facilitar isso, o Radiance fornece um mecanismo de opções bastante genéricas. As opções são divididas por um tipo de opção que designa a qual a Macro de Definição pertence. O Radiance fornece os tipos de opções de api e page para fora da caixa.
Cada opção possui uma palavra -chave para um nome e uma função de expansão que deve aceitar vários argumentos, dependendo do tipo de opção. Sempre fornecidos como argumentos são o nome da coisa que está sendo definida, a lista de formas do corpo da definição e um valor final, opcional, que foi fornecido à opção na lista de opções, se foi mencionada. Essa função de expansão é então responsável por transformar as formas do corpo da macro de definição de alguma forma. Ele também pode emitir uma segunda forma que é colocada fora da própria definição, a fim de permitir a criação do ambiente de alguma maneira.
Consulte option , option-type , name , expander , option , remove-option , list-options , define-option , expand-options
O conceito de um módulo é essencial para o brilho. Serve como representação de uma "parte" do todo. Em um nível técnico, um módulo é um pacote que possui metadados especiais anexados a ele. É fornecido pelo sistema modularize e é usado para facilitar ganchos e gatilhos, interfaces e o rastreamento de algumas outras informações.
O que isso significa para você é que, em vez de um defpackage padrão, você deve usar um formulário define-module para definir seu pacote primário. A sintaxe é a mesma que defpackage , mas inclui algumas opções extras como :domain , que permite especificar o domínio principal no qual esse módulo deve operar (se houver).
O sistema de módulos também permite a amarração de um sistema ASDF a um módulo. Se isso for feito, o sistema ASDF se tornará um "módulo virtual". Para fazer isso, você deve adicionar três opções à definição do seu sistema:
:defsystem-depends-on (:radiance)
:class "radiance:virtual-module"
:module-name "MY-MODULE"
Isso permite que o Radiance identifique e associe as informações do sistema ASDF ao seu módulo. Para a criação automatizada das definições necessárias do sistema e do módulo para um novo 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
Um dos mecanismos que o Radiance fornece para permitir a integração de módulos entre si são os ganchos. Os ganchos permitem que você execute uma função arbitrária em resposta a algum tipo de evento. Por exemplo, um software de fórum pode configurar um gancho que é acionado sempre que uma nova postagem é criada. Uma extensão pode então definir um gatilho nesse gancho que executa tarefas adicionais.
Um gancho pode ter um número arbitrário de gatilhos definidos, mas você deve garantir que um gatilho não demore muito, pois o acionamento de um gancho é uma operação de bloqueio que não terminará até que todos os gatilhos tenham concluído. Como tal, uma operação de gatilho de longa duração pode atrasar uma resposta de solicitação por muito tempo.
Às vezes, os ganchos devem funcionar mais como interruptores, onde podem estar "ligados" por um longo tempo, até que sejam desligados "novamente mais tarde. Se novos gatilhos forem definidos durante esse período, eles devem ser chamados automaticamente. É isso que o define-hook-switch facilita. Produz dois ganchos. Depois que o primeiro foi acionado, qualquer gatilho definido nele mais tarde será chamado automaticamente até que o segundo gancho seja acionado. Isso permite que os gatilhos em ganchos como server-start funcionem corretamente, mesmo que o gatilho seja definido apenas após o início do servidor.
Consulte list-hooks define-trigger remove-hook define-hook-switch define-hook trigger remove-trigger
Para evitar se tornar monolítico e para permitir back -ends extensíveis, o Radiance inclui um sistema de interface. No sentido mais geral, uma interface prevê uma promessa de como algumas funções, macros, variáveis etc. devem funcionar, mas na verdade não as implementam. A funcionalidade real que faz com que tudo o que a interface descreve é empurrado para uma implementação. Isso permite que os usuários codifiquem em uma interface e utilizem sua funcionalidade fornecida, sem se vincular a nenhum back -end em particular.
Para um exemplo concreto, digamos que haja uma interface para um banco de dados. Isso é sensato, pois existem muitos tipos diferentes de bancos de dados, que oferecem muitas maneiras diferentes de interação, mas ainda assim também oferecem algumas operações muito comuns: armazenando dados, recuperando dados e modificando os dados. Assim, criamos uma interface que oferece essas operações comuns. Em seguida, está de acordo com uma implementação para um tipo específico de banco de dados fazer com que as operações reais funcionem. Como escritor de aplicativos, você pode usar a interface do banco de dados e, com ele, fazer com que seu aplicativo funcione automaticamente com muitos bancos de dados diferentes.
Além de dar uma vantagem aos escritores de aplicativos, a dissociação que as interfaces fornecem também significa que um administrador do sistema pode escrever sua própria implementação com relativa facilidade, caso seus requisitos específicos não sejam atendidos pelas implementações existentes. Graças à opacidade das interfaces, uma implementação pode fornecer uma ponte a algo que é executado no processo LISP e algo que é completamente externo. Isso deixa muita escolha aberta para o administrador de um sistema de produção para permitir que eles escolham exatamente o que precisam.
Na prática, as interfaces são tipos especiais de módulos e, portanto, tipos especiais de pacotes. Como parte de sua definição, eles incluem uma série de definições para outras ligações, como funções, variáveis etc. Como é um pacote, como usuário, você pode usar os componentes da interface, assim como usaria qualquer outra coisa em qualquer outro pacote. Não há diferença. Como escritor de implementação, você simplesmente redefine todas as definições que a interface descreve.
Para realmente carregar um módulo que utiliza uma interface, uma implementação para a interface deve ser carregada com antecedência. Caso contrário, as macros não poderiam funcionar corretamente. Assim, para permitir dependendo das interfaces na definição do sistema ASDF sem precisar se referir a uma implementação específica, o Radiance fornece uma extensão ASDF. Esta extensão possibilita adicionar uma lista como (:interface :foo) à sua :depends-on . O Radiance resolverá a interface para uma implementação concreta quando o módulo for carregado.
O Radiance fornece um monte de interfaces padrão. Cada uma dessas interfaces possui pelo menos uma implementação padrão fornecida pelos Radiance-Contribs. As interfaces são:
adminauthbancachedatabaseloggermailprofilerateserversessionuserAs interfaces são descritas em profundidade abaixo.
Consulte interface , interface-p , implementation , implements , reset-interface , define-interface-extension , find-implementation , load-implementation , define-interface , define-implement-trigger
Para permitir a execução de várias instâncias de brilho com diferentes configurações na mesma máquina, o Radiance fornece o que chama de sistema de ambiente. O ambiente é basicamente o conjunto de arquivos de configuração e tempo de execução para o próprio Radiance e todos os módulos carregados. A configuração do Radiance também inclui o mapeamento da interface para a implementação escolhida e, portanto, decide o que deve ser escolhido se uma interface for solicitada.
O ambiente específico usado é escolhido o mais tardar quando startup é chamada e a mais cedo quando um módulo é carregado. Neste último caso, são fornecidas reinicializações interativas para permitir que você escolha um ambiente. Isso é necessário, de outra forma, o Radiance não será capaz de resolver o mapeamento da interface.
Como parte do sistema ambiental, o Radiance fornece um sistema de configuração que você pode-e provavelmente deve-usar para o seu aplicativo. Ele garante que as configurações sejam devidamente multiplexadas para cada ambiente e que as configurações sejam sempre persistentes. Ele também usa um formato de armazenamento legível por humanos, de modo que os arquivos possam ser lidos e modificados sem a necessidade de ferramentas especiais.
Veja onipresente para o manuseio e as insenstrações de uso reais do armazenamento de configuração. Basta observar que, em vez das funções value , o Radiance fornece funções config .
Além dos arquivos de configuração, o ambiente também fornece locais de armazenamento consistentes para arquivos de dados de tempo de execução, como uploads do usuário, arquivos de cache e assim por diante. Você pode recuperar esse local usando environment-module-directory e environment-module-pathname . Ao armazenar uploads e caches, um módulo deve usar esses caminhos para apresentar uma interface consistente ao administrador.
Em um sistema implantado, pode-se desejar alterar a localização dos caminhos de armazenamento ambiental; nesse caso, o administrador é incentivado a fornecer novos métodos sobre environment-directory e environment-module-directory para personalizar o comportamento conforme desejado. Veja também as seqüências de documentação associadas para obter mais detalhes e ações padrão.
O ambiente também permite substituir o administrador. Usando os tipos :static e :template para environment-module-directory o caminho fornece o caminho para armazenar arquivos que devem substituir o modelo padrão e os arquivos estáticos de um módulo. Os caminhos dentro dos respectivos diretórios precisam corresponder aos dos próprios arquivos de origem do módulo. Observe que os arquivos estáticos e de modelo que um módulo realmente utiliza são armazenados em cache no tempo de carregamento do módulo e, portanto, não alterará a menos que a imagem Lisp seja reiniciada, ou os arquivos de origem do módulo estão recarregados.
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 , modelo, @template , static-file @static
Às vezes, os sistemas evoluem de maneiras incompatíveis com versões anteriores. Nesse caso, para as configurações existentes continuam funcionando com a nova versão, a migração de dados de tempo de execução é necessária. A Radiance oferece um sistema para automatizar esse processo e permitir uma atualização suave.
A migração entre as versões deve ocorrer automaticamente durante a sequência de inicialização do Radiance. Como administrador ou autor, você não precisa executar nenhuma etapa adicional para que as migrações ocorram. No entanto, como autor do módulo, você naturalmente precisará fornecer o código para executar as etapas de migração de dados necessárias para o seu módulo.
Para que um módulo seja migrável, ele precisa ser carregado por um sistema ASDF que possui uma especificação de versão. A versão deve seguir o esquema de número pontilhado padrão, com um hash de versão opcional que pode ser adicionado no final. Você pode definir etapas de migração entre as versões individuais usando define-version-migration . Depois de definido, o Radiance captará automaticamente versões de concreto e executará as migrações necessárias em sequência para atingir a versão de destino atual. Para obter mais informações sobre o procedimento preciso e o que você pode fazer, consulte migrate e migrate-versions .
Veja last-known-system-version , migrate-versions , define-version-migration , ready-dependency-for-migration , garantir as versions , ensure-dependencies-ready , migrate
Finalmente, o Radiance fornece uma sequência de inicialização e desligamento padrão que deve garantir que as coisas sejam configuradas e preparadas corretamente e depois limpadas bem novamente. Uma grande parte dessa sequência é apenas garantir que certos ganchos sejam chamados na ordem adequada e nos momentos apropriados.
Embora você possa iniciar um servidor manualmente usando a função de interface apropriada, você não deve esperar que os aplicativos sejam executados corretamente se fizer isso dessa maneira. Muitos deles esperam que certos ganchos sejam chamados para funcionar corretamente. É por isso que você deve sempre, a menos que saiba exatamente o que está fazendo, use startup e shutdown para gerenciar uma instância de brilho. A documentação das duas funções deve explicar exatamente quais ganchos são acionados e em que ordem. Uma implementação pode fornecer definições adicionais e não especificadas sobre símbolos no pacote de interface, desde que os símbolos não sejam exportados.
Consulte *startup-time* , uptime , server-start , server-ready , server-stop , server-shutdown , startup , startup-done , shutdown shutdown-done started-p
Essas interfaces são distribuídas com brilho e fazem parte do pacote principal. As bibliotecas podem fornecer interfaces adicionais, no entanto. Para implementações de interfaces padrão, são permitidos os seguintes relaxamentos de restrições de definição de interface:
As listas de lambda que contêm &key argumentos podem ser estendidos por mais, argumentos de palavras-chave dependentes da implementação. As listas de lambda que contêm &optional , mas não &key ou &rest podem ser estendidas por mais argumentos opcionais. As listas de lambda que contêm apenas argumentos necessários podem ser estendidos por argumentos adicionais ou de palavras-chave.
Esta interface fornece uma página de administração. Ele deve ser usado para qualquer tipo de configurações configuráveis pelo usuário ou exibição de informações do sistema. Observe que, apesar de ser chamado de "administração", isso não se destina apenas aos administradores do sistema. A página deve ser utilizável para qualquer usuário.
A página de administração deve poder exibir um menu categorizado e um painel. Os painéis são fornecidos por outros módulos e podem ser adicionados através admin:define-panel . Os painéis que fornecem acesso a operações confidenciais devem ser restritos adequadamente através da opção :access e uma permissão não-defensiva. Consulte a interface do usuário para uma explicação sobre as permissões.
Para vincular a página de administração ou um painel específico, use o tipo de recurso page .
Consulte admin:list-panels , admin:remove-panel , admin:define-panel , admin:panel
A interface de autenticação é responsável por vincular um usuário a uma solicitação. Por esse motivo, ele deve fornecer alguma maneira pela qual um usuário possa se autenticar contra o sistema. Como isso é feito exatamente está à altura da implementação. A implementação deve, no entanto, fornecer uma página na qual o processo de autenticação é iniciado. 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: