Radiance - Common Lisp Webアプリケーション開発環境


原著者の許諾のもと、翻訳・掲載しています。

Radiance: A Common Lisp web application environment , written by Shirakumo


Radianceについて

Radianceは、Webアプリケーションの開発環境です。Webフレームワークの一種ですが、汎用的で、変更も容易です。 特別な変更を加える必要なく、WebサイトやWebアプリを簡単に書くことができます。

入手するには

Radiance、また関連するモジュールやアプリケーションは、Quicklispを通して配布されています。Radianceをインストールするには次のようにしてください。

(ql-dist:install-dist "http://dist.tymoon.eu/shirakumo.txt")
(ql:quickload :radiance)

以上で、Quicklispのquickloadコマンドで、Purplish等のRadianceモジュールを利用できるようになります。

チュートリアル

Radianceのチュートリアルはこちらです。Radianceの重要コンセプトや、Webアプリの書き方を紹介しています。チュートリアルを通して、Radianceの使い方に慣れることができます。また、特定の機能が必要なときに、どこを調べるべきかが分かります。チュートリアルの最後では、本番環境でのRadianceのインストールとデプロイ方法について説明しています。

簡単な例

あなたが一番したいことは、ユーザにHTMLを提供することでしょう。その方向に進めながら、少しずつ拡張していきます。まずは、Radianceの準備をします。

(ql:quickload :radiance)
(radiance:startup)

初めてRadianceを使う場合には、r-welcomeモジュールを使ってください。r-welcomeモジュールを使うと、ブラウザーでの最初のページにリンクさせることができます。まずは、小さなページにリンクをはりましょう。

(in-package :rad-user)

(define-page example "/example" ()
  (setf (content-type *response*) "text/plain") 
  "Hi!")

localhost:8080/exampleにアクセスすると、"Hi"と表示されるはずです。これでは、かなり退屈ですね。では、cl-whoを用いてHTMLを出してみましょう。

(ql:quickload :cl-who)
(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.")))))))

普通であれば、フォントの書式を変えたり、CSSファイルを追加しながら、ページのスタイルを作っていきます。CSSファイルをサーブすることは可能ですが、長い目でみれば、最適の方法とはいえません。

ここでは、モジュール(module)を作る方法をご紹介します。モジュールを使うことで、Webページを適切に整理することができます。モジュールに必要なファイルは手動でも作れますが、ここでは雛形でモジュールを自動生成します。

(create-module "example")

モジュールが自動生成されたあと、モジュールへのパスが返されます。生成されるものは、ASDFのシステムファイル、lispのmainファイル、2つのフォルダ(statictemplate)です。

staticフォルダには、静的にサーブされるファイルが入ります。templateには、テンプレートエンジン関連のファイルが入ります。

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.")))))))

ページは、シンボル名によって特定されます。自分のモジュールを作ると、パッケージが生成されます。前の例のシンボルは、一度も使われていないシンボルです。名前衝突を避けるために、rad-userパッケージでページを消す必要があるかもしれません。

(remove-page 'rad-user::example)

次に、簡単なCSSファイルを作りましょう。staticフォルダに、example.cssという名前で作成してください。自分でCSSを書くのが面倒な方は、次のCSSコードをお使いください。

body{
    font-family: sans-serif;
    font-size: 12pt;
    background: #EEE;
}

header{
    text-align: center;
}

main{
    width: 800px;
    margin: 0 auto 0 auto;
    background: #FFF;
    padding: 10px;
    border: 1px solid #BBB;
    border-radius: 5px;
}

では、CSSファイルにリンクするように、HTMLを修正しましょう。アドレスがスタイルシートにたどり着くために、後の章ではルーティングシステムを使いますが、今はその必要はありません。焦らずに、進めていきましょう。

(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.")))))))

ページを再度読み込むと、スタイルが適用されているはずです。uri-to-urlが動作する原理については、後ほど詳しく説明します。大切なことは、どのようにセットアップしても、静的ファイルへのリンクは適切に解決されるということです。

1. Radianceのコンセプトと部品

1.1 URI

Radianceの中心的なコンセプトはURIです。URIはオブジェクトであり、ドメインポート番号(オプション)パスを含むリストで構成されます。

RadianceのURIは、一般的なURIから要素を抽出したもので、スキーマ、クエリ、フラグメント等は含みません。また重要な違いとして、domainsのURIは、フレームワークにおいて、複数の場所で使われます。例えば、locationを捕捉するときや、dispatch matchingを処理するために使われます。

URIは変更可能です。URIの修正は、クリティカルパスがある場所で行われるので、パフォーマンス上、重要です。想定外の方法でURIを修正した場合、予期しない動作が発生する可能性があります。

URIは、単一の文字列で表現されます。文字列にシリアライズされて、完全なURIオブジェクトにパーズして戻すことも可能です。URIはFASLファイルにリテラルで書き出されるので、マクロから吐き出してもいいです。URIのシンタックスは、次の通りです。

URI     ::= DOMAINS? (':' PORT)? '/' PATH?
DOMAINS ::= DOMAIN ('.' DOMAIN)*
DOMAIN  ::= ('a'..'Z' | '0'..'9' | '-')
PORT    ::= ('0'..'9'){1, 5}
PATH    ::= .*

URIをURLに変換するために、uri-to-urlを使います。uri-to-urlを使うと、反転(reversal)、エンコーディング、フォーマットの修正は自動で行われます。

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を参考にしてください。

1.2 リクエストとレスポンス

行き来するデータを格納しておくために、requestオブジェクトとresponseオブジェクトがあります。requestオブジェクトは、リクエストがどこに向かうのかを表すURI、POST、GET、ヘッダ、クッキーなどのHTTP通信のペイロードデータを全て保持します。responseオブジェクトは、リターンコード、ヘッダー、クッキー、BODYデータを保持します。

リクエストが行われている間、これらの2つのオブジェクトが必ず存在し、*request**response*に束縛されなければいけません。それらは、動的なページを生成するために必要な情報を多く保持しています。さらに、リクエストはdataテーブルを含んでおり、任意のデータを保持することができます。これは、システム内の各々の部品の間で、リクエストの実行中に取得されるような情報をやりとりするときに役立ちます。

リクエストは、必ずしもHTTPサーバから来る必要はありません。動作をテストするために、プログラムからリクエストを送ることも可能です。どのような場合であっても、リクエストをディスパッチするインターフェイスはrequestと呼ばれます。この仕組みは、リクエストとレスポンスを構築して、URIを適切に処理します。もし自分自身でリクエストオブジェクトを送りたいのであれば、execute-requestを使うこともできます。

リクエスト処理に関する詳しい情報は、dispatcher、pages、API endpointをご参照ください。

*request*, *response*, *default-external-format*, *default-content-type* , request, uri, http-method, headers, post-data, get-data, cookies, user-agent, referer, domain, remote, data, issue-time, response, data, return-code, content-type, external-format, headers, cookies, cookie, name, value, domain, path, expires, http-only, secure, cookie-header, cookie, get-var, post-var, post/get, header, file, redirect, serve-file, request-run-time, *debugger*, handle-condition, render-error-page, execute-request, set-data, requestも参考にしてしてください。

1.3 ルーティング

リクエストがディスパッチされる前には、ルーティングシステムを通過します。他のフレームワークでは'routes'はどのハンドラがリクエストを処理するかを指定しますが、Radianceではその方式とは異なります。Radianceにおいてルート(route)とは、URIを変換するフォームです。この部分は、2つの世界を作成して保持します。内部の世界外部の世界です。

内部の世界は、実際にWebアプリケーションが住む世界です。外部の世界は、HTTPサーバとWebサイトを利用するユーザが住む世界です。この区別は、サーバの潜在的な罠を避けてWebアプリケーションを書くために必要不可欠です。あなたのアプリケーションを動作させるために、どのドメインやポート、パスが必要になるかを心配する必要がありません。一方で、アプリが壊れないように、Webの管理者として、システムをあなたの思う通りにカスタマイズして動作させる必要があります。

そのためには、ルーティングが役に立ちます。ルーティングは、MappingReversalの2種類があります。

Mappingは、外部の世界からくるURIを内部の世界のURIに変換します。普通は、トップレベルのドメインを切り取り、サブドメインにマッピングします。

Reversalは逆のことをします。内部の世界から外部の世界へといきます。このことは、あなたが提供するWebページが、実際に外部からアクセス可能なリソースを参照できるようにするために必要です。

ルーティングは、任意の処理を行うことができます。基本的なレベルでは、何らかの方法でURIを修正する関数です。ルーティングを使うと、強力で柔軟なシステムを構築することができます。アプリケーションの開発者として、external-uriuri-to-urlを、ページ内の全てのリンクに使うようにしてください。

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も参考にしてください。

1.4 URIディスパッチャー

ついに、リクエストに対してコンテンツを生成する段階まできました。URIディスパッチャーはURIのサブクラスであり、名前、関数、優先順位を運びます。

優先順位に基づいたリストは、いつリクエストがきても実行されます。リクエストのURIは、それぞれのディスパッチャーに対応して、最初に対応する最初のディスパッチャー関数が実行されます。たったこれだけです。

ディスパッチャー関数は、ページの内容を提供するために、必要な値を、レスポンスオブジェクトに設定する責任があります。そのためには、レスポンスオブジェクトのdataのフィールドに直接設定するか、関数から適切な値を返します。Radianceは、4つの型のデータ(streampathnamestring(array (unsigned-byte 8)))を受けとります。

もしURIディスパッチャーが明示的な優先順位の番号を持っていない場合、優先順位はURIの特異性によって決まります。どのように計算がされるかについて詳しく知りたい場合は、URIソーティング関数であるuri>をみてください。

uri-dispatcher, name, dispatch-function, priority, uri-dispatcher, remove-uri-dispatcher, list-uri-dispatchers, uri-dispatcher>, define-uri-dispatcher, dispatchもご参照ください。

1.5 Page

ページは、実際にコンテンツを提供する関数を定義するために用いられます。しかし、ページはuriディスパッチャーであり、物事を簡単にするためのマクロをいくつか含んでいます。注目してもらいたいのは、拡張可能なオプションです。では、詳しくみていきましょう。

Radianceには、デフォルトでセットアップされるページがいくつかあります。faviconrobotsのページは、Radianceのstatic/ディレクトリでサーブされます。本番環境のサーバでも、自分のサイトでファイルを提供したり更新したりしたいはずです。

そのような目的を満たすために、staticページの仕組みがあります。staticページは、静的コンテンツをWebアプリケーションとモジュールにサーブします。staticページは、そのドメインでも/static/...のパスで有効であり、最初のディレクトリがモジュールの名前である形式である必要があります。残りは、モジュールのstatic/ディレクトリの範囲にあるパスです。この仕組みにより、CSS、JavaScript、画像などの静的ファイルを参照することができます。

最後に、apiページですが、APIエンドポイントのディスパッチを処理する役割があります。これについては次の章で説明します。ページは、静的ファイルの場合と同様に、どのドメインにおいても/api/...のパスで捕捉して動作します。

page, remove-page, define-pageをご参照ください。

1.6 APIエンドポイント

Radianceは、REST APIとの連携もサポートしています。これは、取ってつけたような機能ではありません。多くの現代のアプリケーションは、そのようなAPIを提供することが多く、Radianceは、APIエンドポイントを含むアプリケーションを書く方法を提案します。

コンセプトとしては、APIエンドポイントとは、ブラウザーのリクエストに応じて呼び出される関数です。そのレスポンスは、リクエスト主が読める形式にシリアライズされます。重要な点は、APIエンドポイントは、ユーザからも、プログラムからも利用可能であるべきということです。APIを通してプログラムから実行されるアクションは全て、ユーザによって実行されるものであるので、Radianceでは、両方から利用可能にするよう推奨しています。重複を避けるために、これら2つは、1つとして扱われます。

データの修正作業は全て、APIエンドポイントを通して提供されます。リクエストをしたのがユーザプログラムのどちらなのかを区別せずに処理されます。ユーザがリクエストを行う場合は、適切なページにリダイレクトを行います。プログラムからリクエストが行われた場合は、読み込めるフォーマットで、データのペイロードが提供されます。

全てのパートのうち、APIフォーマットのシステムですが、データを特別なフォーマットにシリアライズすることを担当します。デフォルトでは、S式を基本とするフォーマットが提供されていますが、JSON形式の出力も簡単にロードできます。

次に、browserのPOST/GETパラメータの仕様をみましょう。そのパラメータが"true"という文字列を含む場合、APIリクエストはユーザからであると扱われて、データのペイロードはされずにリダイレクトされます。

あなたのアプロケーションは、統一されたAPIを提供するために、これらをうまく使う必要があります。今、実際のエンドポイントの定義は、名前、関数、引数を記述するラムダリスト、リクエストのパーズ関数で構成されます。引数の典型としては、必須の引数かオプショナル引数が理にかなっています。結局、HTTPリクエストは、キーワード引数しかもつことができません。キーワード引数は、あってもなくてもいいです。

APIエンドポイントの名前は、どこにリーチできるかを示す識別名として機能します。APIエンドポイントは、/api/パスに存在し、エンドポイントの名前もそれに従います。エンドポイントを修正する際には、あなたのモジュールやアプリケーションが、他のエンドポイントをうっかり踏み外さないように気をつけなければいけません。これはURIディスパッチのときとは違いますが、理由は、APIエンドポイントは、曖昧さやパスのプロセスを許可しないからです。なので、全てのエンドポイントは、唯一のパスをもなければいけません。唯一のパスは、直接、名前としてサーブします。

生の(raw)関数は、APIがインターフェイスを提供する関数です。生の関数は、リクエストされたアクションを行い、適切なデータを上で述べた通りに返す役割を果たします。フォーマットされたAPIのデータを返すためには、api-outputを使います。ブラウザリクエストからリクエストを受けてリダイレクトをするには、redirectを使います。

最後に、リクエストのパーズ関数は、リクエストオブジェクトを受け取り、関数が必要な実引数を抽出して、最終的に、可能であれば、適切な引数を適用させて関数を呼び出します。パーズ関数は、もし必要な引数が見当たらない場合、api-argument-missingエラーの信号を送ります。無駄な実引数は無視されます。

call-apiを用いると、プログラムからAPIエンドポイントを呼び出すこともできます。call-api-requestを使うと、Requestをシュミレーションすることができます。どちらもURIディスパッチの仕組みを通る必要はありません。

ページと似ていますが、APIエンドポイントの定義は、定義を簡単にするために、拡張オプションも受け付けます。オプションについては、次で説明します。

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をご参照ください。

1.7 オプション

オプションは、拡張可能な定義のマクロを提供します。これは、フレームワークが何か定義する際に、共通のオペレーションをより短しようと拡張しようとするときに役に立ちます。例えば、アクセス権があるユーザに対して、ページかAPIエンドポイントを制限するような共通するタスクがあるとします。

このような実装を簡単にするために、Radianceは一般的なオプションに仕組みを提供します。オプションは、定義のマクロが属するオプションの型によって分けられます。Radianceは、apipageのオプションを提供します。

それぞれのオプションは、オプションの型に応じて、名前と多くの引数を受け入れる関数のために、キーワードをもっています。定義名、ボディー部、最終にオプションに渡される値を含んだリストが、いつも引数として与えられます。

この拡張用(expansion)の関数は、定義マクロのボディーの式を変換します。環境の設定を許可するために、定義自体では含まれない式を吐き出すこともできます。

option, option-type, name, expander, option, remove-option, list-options, define-option, expand-optionsをご参照ください。

1.8 モジュール

モジュールの概念は、Radianceにおいて必要不可欠です。全体を構成する部品として働きます。技術レベルでいうと、モジュールは、特別なメタデータが与えられたパッケージです。モジュールは、modularizeしステmyによって提供され、フック、トリガー、インターフェイス等を使いやすくして、他の情報をトラッキングするために使われます。

defpackageを使う代わりに、define-moduleを使うようにしてください。シンタックスはdefpackageですが、:domainのような特別なオプションを含んでいるので、モジュールが機能するプライマリ・ドメインを特定することができます。

モジュールのシステムでは、ASDFのシステムをモジュールに記すこともできます。もし記せば、そのASDFシステムは仮想のモジュールになります。このようにするためには、システムの定義に3つのオプションを追加する必要があります:

:defsystem-depends-on (:radiance)
:class "radiance:virtual-module"
:module-name "MY-MODULE"

こうすることで、Radianceは、ASDFのシステム情報を特定して、あなたのモジュールに関連づけることができます。 新しいモジュールのために、必須のシステムとモジュールの定義を自動で行うには、create-moduleをみてください。

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をご参照ください。

1.9 フック

Radianceには、互いのモジュールを連携させるために、フック(hook)の仕組みがあります。フックを使うと、ある種のイベントに反応して、任意の関数を実行することができます。例えば、意見交換をするようなソフトウェアでは、新しい投稿が作成されるごとにトリガーされるフックを設定することがでいます。拡張では、追加のタスクを実行するためにトリガーをフックに対して定義できます。

フックは、任意の数のトリガーをもつことができますが、トリガーは長すぎないようにしてください。フックをトリガーすることは、全てのトリガーが終了するまでブロックされる動作だからです。そのようなわけで、長い間続くようなトリガーの実行は、リクエストへのレスポンスを遅らせてしまう可能性があります。 long.

フックは、長い間はonであり、後にoffになるスイッチにような関数であるべきです。もし新しいトリガーが実行中に定義された場合は、自動で呼び出されるべきです。これは、define-hook-switchが容易にすることです。define-hook-switchは2つのフックを作ります。1つ目がトリガーされると、それに定義されているトリガーは、後に2つ目のフックがトリガーされたときに、自動で呼び出されます。このおかげで、仮にトリガーがサーバの起動後に定義されたとしても、server-startが適切に動作します。

list-hooks, define-hook, remove-hook, define-trigger, remove-trigger, trigger, define-hook-switchをご参照ください。

1.10 インターフェイス

システムが一枚岩になることを避けるために、バックエンドは拡張できるようにするために、Radianceはインターフェイスの仕組みを含んでいます。一般的な意味では、インターフェイスとは、関数やマクロ、変数がどのように動作するかについて、約束事を決めますが、実際に実装するのはインターフェイスではありません。インターフェイスが動作する全てを作っている実際の機能性は、実装の外側にあります。このことにより、ユーザはインターフェイスに対してコードを書くことができ、特定のバックエンドに結びつけることなく、与えられた機能を利用することができます。

具体例として、データベースのためのインターフェイスをみていきましょう。データベースには多くの種類があり、全ては違う方法でやりとりをしますが、データの保存、検索、修正等、どれにも共通する操作があるので、このインターフェイスは実用的です。

では、共通する操作を提供するインターフェイスを作ります。これは、特定の種類のデータベースが実際に動かすためにすることは、実装次第です。アプリケーションの作者として、データベース・インターフェイスを活用することができます。このインターフェイスを使えば、様々なデータベースに対して自動的にうまく動作するようにできます。

これは、アプリケーション作者に有利なだけでなく、インターフェイスで分離することにより、システム管理者は、比較的容易に、実装に存在しない特殊な機能を自分で実装することができることです。インターフェイスが不透明であるおかげで、実装はLispのプロセスで動くものと、外部で動くプロセスとの橋渡しをすることができます。 これは、Productionシステムの管理者が必要な情報を選び出すために、開かれた選択肢を多く与えます。

実際、インターフェイスは特別な種類のモジュールであり、特別な種類のパッケージです。定義の一部として、関数や変数などの束縛のために、一連の定義を含みます。インターフェイスはパッケージなので、あなたがコンポーテントで使えるユーザは、他のパケージにあるものも何でも使えます。違いはありません。実装の作者として、あなたはインターフェイスが示す定義を再定義することができます。

実際にインターフェイスを利用するモジュールを読み込むためには、インターフェイスの実装は、事前に読み込まれる必要があります。そうでなければ、マクロは適切に動作しません。あなたのASDFシステムの定義において、特定の実装を参照する必要なく、インターフェイスに依存することを許可するためには、Radianceは拡張されたASDFを提供します。この拡張を使うと、(:interface :foo)のようなリストを:depends-onに追加することができます。モジュールがロードされたときに、Radianceはインターフェイスを具体的な処理に分解します。

Radianceは、標準のインターフェイスを提供します。それぞれのインターフェイスは、radiance-contribsによって提供される標準実装を1つ以上もちます。インターフェイスは次の通りです:

  • admin 拡張可能な管理者ページを提供します。

  • auth 認証とログインに関する全てを扱います。

  • ban IPアドレスによってユーザがサイトにアクセスすることを禁止します。

  • cache キャッシュのための仕組みを提供します。

  • database 柔軟なデータベースのインターフェイスです。オブジェクト保存、リレーショナルデータベースをバックエンドとして利用できます。

  • logger ログ出力のためのインターフェイスです。デバッグやメッセージを出力できます。

  • mail メールを送るための最小限のインターフェイスです。

  • profile ユーザのプロフィールや属性を拡張するために使います。

  • rate 特定のリソースにrate limitationを許可します。

  • server Radianceを外部の世界と結びつける架け橋の役割を果たすインターフェイスです。

  • session トラッキングするために、持続するセッションを保証します。

  • user ユーザアカウントとパーミションの機能を提供します。

それぞれのインターフェイスについては、次の章で説明します。

interface, interface-p, implementation, implements, reset-interface, define-interface-extension, find-implementation, load-implementation, define-interface, define-implement-triggerをご参照ください。

1.11 環境

複数のRadianceインスタンスに対して、同じマシン内で異なる設定ができるように、Radianceでは環境(Environment)という仕組みを用意しています。環境とは、基本的には、Radianceとロードされるモジュールのための設定ファイルの一式です。Radianceの設定は、インターフェイスを選択される実装にマッピングすることで、もしインターフェイスが求められたときに選択されるように決定します。

startupが呼び出された時、どれだけ遅くとも、特定の環境が選択されます。早ければ、モジュールがロードされたときに選択されます。後者の場合は、環境を選ぶために、インタラクティブな再起動が可能です。これは必須の機能ですが、理由は、そうでなければ、Radianceがインターフェイスのマッピングを解決できないからです。

環境のシステムの一部として、Radianceは、あなたのアプリケーションで使える(おそらく、使うべき)設定システムを提供します。設定システムを使うと、それぞれの環境ごとに、適切に設定することができます。その設定は、いつでも持続性があるうえ、可読性にも優れたフォーマットで保存されるので、特別なツールで読み込んだり、修正したりする必要はありません。

実際に設定手順でどのように保存処理がされているかを知りたい方は、ubiquitousを参考にしてください。 value関数の代わりに、Radianceではconfig関数を使えます。

environment-change, environment, check-environment, mconfig-pathname, mconfig-storage, mconfig, defaulted-mconfig, config, defaulted-configをご参照ください。

1.12 インスタンスの管理

最後に、Radianceは、起動からシャットダウンまでのシーケンスを提供しています。このおかげで、ソフトは適切に起動して利用可能になり、その後、綺麗に片付けて終了することを確かにします。

そのシーケンスの大部分は、正しい順番で、適切な回数、特定のフックが呼び出されることによって実現されています。

インターフェイスの関数を適切に使うことで、サーバを手動で起動することは可能ですが、その方法で、アプリケーションが正しく動作すると想定するのはやめてください。多くは、特定のフックが適切な順番で呼び出されることを要求しています。このような理由で、startupshutdownでRadianceインスタンスを管理する必要があります。startupshutdownのドキュメントには、どのフックが、どの順番で呼び出されているかが説明されています。実装では、シンボルがexportされていない限り、追加で特定されない定義をインターフェオスのパッケージのシンボルに加えることができます。

*startup-time*, uptime, server-start, server-ready, server-stop, server-shutdown, startup, startup-done, shutdown, shutdown-done, started-pをご参照ください。

2. 標準のインターフェイス

標準のインターフェイスは、Radianceとcore packageと一緒に配布されています。ライブラリは、追加のインターフェイスを提供することも可能です。インターフェイスの実装ですが、インターフェイス定義では、次の制限の緩和が許可されています:

&key引数を含むラムダリストは、実装依存のキーワード引数を使って拡張できます。&optional引数を含み、&key&restを含まないラムダリストは、オプショナル引数で拡張できます。必須の引数しか含まないラムダリストは、オプショナル引数かキーワード引数で拡張できます。

2.1 管理

管理・インターフェイスは、管理者ページを作成するためのものです。ユーザ構成の設定やシステム情報の表示のために使えます。"管理(administration)"という名前ですが、システムの管理者だけに限ったものではありません。どのユーザにも利用できることができます。

管理者ページは、カテゴリー分けされたメニュやパネルを表示できなければいけません。パネル群は、他のモジュールによって提供されており、admin:define-panelで追加できます。秘密情報を含むようなパネルにアクセスするパネルは、:accessオプションでアクセスを禁止させるようにしてください。

パーミッションについては、 userインターフェイスを参照してください。

管理者ページや特定のパネルにリンクをはるには、pageリソースを使ってください。

admin:list-panels, admin:remove-panel, admin:define-panel, admin:panelをご覧ください。

2.2 認証

認証・インターフェイスは、ユーザをリクエストと結びつけます。そのために、ユーザがシステムに対して自分自身を認証させる方法を提供しなければいけません。どのように実現するかは、実装次第です。実装は、認証のプロセスが初期化するためのページを提供しなければいけません。pageのソースを通してURIを認証プロセスに渡して、"login"を引数として渡します。

現在リクエストに結び付けられているユーザをauth:currentコマンドで調べることができます。ユーザが"anonymous"と解釈される場合は、NILを返します。詳しくはuserインターフェイスを参照ください。

auth:*login-timeout*, auth:page, auth:current, auth:associateもご覧ください。

2.3 ban

ban・インターフェイスは、IPによるアクセス制限を提供します。IP BANされたクライアントのIPアドレスからは、リクエストするページに対してアクセス出来なくなります。BANは、タイムアウトの後、手動・自動いずれでも、離す(lift)することができます。実装としては、ユーザのIPを監視するために、追加で労力を割くことは想定していません。

ban:jail, ban:list, ban:jail-time, ban:releaseをご参照ください。

2.4 キャッシュ

キャッシュ・インターフェイスは、一般的なキャッシュの仕組みを提供します。カスタマイズ可能で無効化のテストをもっています。キャッシュを明示的に新しくするには、cache:renewをつかいます。cache:with-cacheを使うと、キャッシュの部品を構築することができ、テストのformがtrueと評価されたときに、BODYのキャッシュ値が返されるようにできます。

キャッシュ値が保存される方法は実装によります。 cache:getcache:with-cacheを使うと、キャッシュ値は文字列かバイト列に強制変換されます。実装では、キャッシュに格納できる typeには制限がありませんが、少なくとも、文字列とバイト列はサポートします。

キャッシュ値の名前は、名前とパッケージ名が、次の文字を含まないシンボルでなければいけません:<>:"/\|?*. キャッシュ値の変形は、princによって表示される表現とは区別されるオブジェクトである必要があります。その際には、先ほどと同じ文字の制限が適用されます。

cache:get, cache:renew, cache:with-cacheをご参照ください。

2.5 データベース

データベース・インターフェイスは、データを持続させるためのレイヤーを提供します。通常は、データベースと呼ばれるレイヤーです。リレーショナル型のデータベースである必要はありませんが、そうであってもいいです。実装の変数を保持するために、基本的なデータベースの機能しかサポートされません。(joinsやtriggers等はありません)データ型も、整数、float、文字列に限定されます。これらの制限にも関わらず、多くのアプリケーションにおいて、データベースインターフェイスはとても役に立ちます。

伝統的なRDMBの用語と区別するために、特別な用語が使われます: schemaは"structure"、tableは"collection"、rowは"record"とします。

データベースに接続する前に、データベース関連の命令を実行すると、未定義の動作を招きます。Radianceでは、Radianceが動作している間はデータベースが接続されることを保障しているので、どのページ、どのAPI、どのURIディスパッチャーの定義において、データベースインターフェイスを問題なく使えます。

実際にデータ保存を行うための関数は、db:insertdb:removedb:updatedb:selectdb:iterateです。 それらは、あなたが期待するように動作するはずです。

詳しくは、それぞれの関数に書かれているコメントを読んでください。コレクションの作り方、どのような制限があるかを知りたい方は、db:createのコメントも参考にしてください。

データベースは、データ操作が完了すれば、Radianceを再起動したり、Lispイメージ、マシンがクラッシュしたとしても、データの変更は存続されなければいけません。

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を参照してください。

2.6 logger

logger・インターフェースは、ログの関数を提供します。システムの中で関連して起きていることについて、ログのメッセージを出すことができます。ログの出力内容と出力方法に関しては、実装とシステムの管理者次第です。

logger:log, logger:trace, logger:debug, logger:info, logger:warn, logger:error, logger:severe, logger:fatalをご覧ください。

2.7 メール

メール・インターフェースは、メールを送る仕組みを組み込むことができます。様々なコンポーネントが、Webサイトの外からユーザとつながるために、メールのアクセスが必要になるかもしれません。リモートサーバ、ローカル環境でのメール送信等、メールの送信方法の設定は、処理系依存です。mail:sendのフックを使うと、メールが送られる前に、メールに反応することができます。

mail:sendをご覧ください。

2.8 プロフィール

プロフィール・インターフェイスは、userにある種のpresenceをもたせたいアプリケーションにおいて、共通で使用されるuserインターフェイスを拡張できるようにします。そのインターフェイスは、機能の一部として、ユーザのプロフィールが表示されるページを提供する必要があります。そのプロフィールは、数種類のパネルを表示しなければいけません。パネルは、他のモジュールによって提供されており、profile:define-panelで追加できます。

pageのリソースの型を通して、URIをプロファイルのページに移動することができます。

そのインターフェイスは、視覚的にユーザを特定させるためにprofile:avatarアバター画像にアクセスさせることもできます。また、profile:nameを使うと、ユーザがユーザ名をカスタマイズできます。さらに、profile:fieldsprofile:add-fieldprofile:remove-fieldを使うと、どのようなデータをユーザの属性に含むか、それを公(public)に表示させるかどうかを指定できます。

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をご参照ください。

2.9 rate

rate・インターフェースは、Rate limitationの仕組みを提供します。秘密情報やコストが高いリソースへの負荷の高いアクセスを防ぐことができます。2つの段階があります。第1段階は、rate:define-limitにより、特定のリソースに対して、Rate limitationの動作を定義します。第1段階は、リソースがrate:with-limitationにより保護されます。

もし、特定のユーザからのblockへのアクセスが頻繁すぎる場合は、blockは呼び出されません。制限の定義があるコードが、代わりに実行されます。

Rate limitationは、クライアント、ユーザ、セッションごとですが、グローバルではないことに注意してください。

rate:define-limit, rate:left, rate:with-limitationをご参照ください。

2.10 サーバ

serverインターフェイスとloggerインターフェイスは、Radianceが起動時に順番通りに読み込まれます。HTTPリクエストに応答する責任があります。実装では、リクエストを受け入れて、Radianceのrequest関数に渡す必要があります。その後、responseはリクエスト主に戻されます。

リスナーの動作を特定する引数は実装によることに注意してください。しかし、実装は、(mconfig :radiance :port)で設定されたlocalhostとポートからでアクセスできる標準のリスナーを提供して、radiance:startupで起動できるようにする必要があります。

server:start, server:stop, server:listeners, server:started, server:stoppedをご参照ください。

2.11 セッション

セッション・インターフェースは、あるクライアントが行う複数のリクエストを追跡します。クライアントによっては情報を隠蔽したり偽装している場合があるので、完全にはクライアントを追跡できるとはいえません。しかし、多くのユーザに対しては、うまく動作するはずです。セッション・インターフェイスは、他のインターフェイスや低レイヤーのライブラリの中で使われます。ユーザ認証にような一貫性を保つために使われます。

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をご参照ください。

2.12 ユーザ

ユーザ・インターフェースは、ユーザオブジェクトを永続させ、パーミションの仕組みを組み込めます。ユーザ認証、ユーザの特定、トラッキング等は扱いません。このインターフェイスでは、ユーザオブジェクトを提供するのみであり、パーミション情報が管理されます。

パーミションに関する詳細は、user:userを参照してください。

user:condition, user:not-found, user:user, user:=, user:list, user:get, 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もご覧ください。

参考

© 2018 t-cool