(defparameter *web* (make-instance '<app>)) @route GET "/"(defunindex() (レンダリング #P"index.tmpl")) @route GET "/hello"(defun Say-hello (&key (|name| "Guest")) (形式 nil "こんにちは、~A" |名前|))
すべて。 Caveman2 は最初から書かれました。
これらは注目すべき点です。
ニングルに基づいています
データベース統合あり
新しい別個の構成システム (Envy) を使用
新しいルーティングマクロが追加されました
最もよく寄せられる質問の 1 つは、「ningle と Caveman のどちらを使用すべきですか? 違いは何ですか?」というものでした。 Caveman と ningle があまりにも似ていたため、これらの質問が頻繁に行われたのだと思います。どちらも「micro」と呼ばれるもので、データベースはサポートされていませんでした。
Caveman2 では、Caveman は「マイクロ」Web アプリケーション フレームワークではなくなりました。 CL-DBI をサポートしており、デフォルトでデータベース接続管理を備えています。穴居人は成長し始めました。
Caveman は、Web アプリケーションの共通部分のコレクションであることを目的としています。 Caveman2 では、次の 3 つのルールを使用して意思決定を行います。
拡張可能であること。
実践的になってください。
何も強制しないでください。
穴居人のような生き方に興味があってここに来たんですよね?ここはディズニーランドではありませんが、ここから始めましょう。洞窟に入ってみよう!
Caveman2 は Quicklisp で利用できるようになりました。
(ql:クイックロード:caveman2)
(caveman2:make-project #P"/path/to/myapp/" :author "<あなたのフルネーム>");-> /path/to/myapp/.gitignore を書き込みます; /path/to/myapp/README.markdown を書き込みます。 /path/to/myapp/app.lisp を書き込みます。 /path/to/myapp/db/schema.sql を書き込みます。 /path/to/myapp/shlyfile.lisp を書き込みます。 /path/to/myapp/myapp-test.asd を書き込みます。 /path/to/myapp/myapp.asd を書き込みます。 /path/to/myapp/src/config.lisp を書き込みます。 /path/to/myapp/src/db.lisp を書き込みます。 /path/to/myapp/src/main.lisp を書き込みます。 /path/to/myapp/src/view.lisp を書き込みます。 /path/to/myapp/src/web.lisp を書き込みます。 /path/to/myapp/static/css/main.css を書き込みます。 /path/to/myapp/t/myapp.lisp を書き込みます。 /path/to/myapp/templates/_errors/404.html を書き込みます。 /path/to/myapp/templates/index.tmpl を書き込みます。 /path/to/myapp/templates/layout/default.tmpl の書き込み
これは、アプリケーションの名前が「myapp」であると仮定した例です。サーバーを起動する前に、まずアプリをロードする必要があります。
(ql:クイックロード:myapp)
アプリケーションには、Web アプリケーションを開始/停止するためのstart
およびstop
という名前の関数があります。
(myapp:開始:ポート 8080)
Caveman は Clack/Lack に基づいているため、Hunchentoot、Woo、Wookie など、どのサーバーで実行するかを選択できます。
(myapp:開始:サーバー:hunchentoot:ポート 8080) (myapp:開始:サーバー:fcgi:ポート 8080)
ローカル マシンでは Hunchentoot を使用し、運用環境では Woo を使用することをお勧めします。
clackup コマンドを使用してアプリケーションを起動することもできます。
$ ros install clack $ which clackup /Users/nitro_idiot/.roswell/bin/clackup $ APP_ENV=development clackup --server :fcgi --port 8080 app.lisp
Caveman2 は、ルートを定義する 2 つの方法、 @route
とdefroute
を提供します。どちらでも使用できます。
@route
、cl-annot を使用して定義されたアノテーション マクロです。メソッド、URL 文字列、および関数を受け取ります。
@route GET "/"(defunindex() ...);;名前のないルート。@route GET "/welcome"(lambda (&key (|name| "Guest")) (形式 nil "ようこそ、~A" |名前|))
これは、引数リストを除いて、Caveman1 の@url
に似ています。引数が必要ない場合は、指定する必要はありません。
defroute
単なるマクロです。 @route
と同じ機能を提供します。
(デフォルトインデックス "/" () ...);;名前のないルート。(defroute "/welcome" (&key (|name| "Guest")) (形式 nil "ようこそ、~A" |名前|))
Caveman は ningle をベースとしているため、Sinatra のようなルーティング システムも備えています。
;; GET リクエスト (デフォルト)@route GET "/" (lambda() ...) (defroute ("/" :method :GET) () ...);; POST request@route POST "/" (lambda() ...) (defroute ("/" :method :POST) () ...);; PUT request@route PUT "/" (lambda() ...) (defroute ("/" :method :PUT) () ...);; DELETE request@route DELETE "/" (lambda() ...) (defroute ("/" :method :DELETE) () ...);;オプション request@route オプション "/" (lambda() ...) (defroute ("/" :method :OPTIONS) () ...);;すべてのメソッド@route ANY "/" (lambda () ...) (defroute ("/" :method :ANY) () ...)
ルート パターンには、引数に値を入れるための「キーワード」が含まれる場合があります。
(defroute "/hello/:name" (&キー名) (形式 nil "Hello, ~A" 名))
「/hello/Eitaro」または「/hello/Tomohiro」にアクセスすると上記のコントローラが呼び出され、 name
適宜「Eitaro」または「Tomohiro」になります。
(&key name)
は Common Lisp のラムダリストとほぼ同じですが、常に他のキーを許可する点が異なります。
(defroute "/hello/:name" (&rest params &key name) ;; ... )
ルート パターンには「ワイルドカード」パラメータが含まれる場合もあります。 splat
使用してアクセスできます。
(defroute "/say/*/to/*" (&key splat) ; /say/hello/to/world に一致します (format nil "~A" splat));=> (hello world)(defroute "/download/*.*" (&key splat) ; /download/path/to/file.xml と一致します (フォーマット nil "~A" スプラット)) ;=> (XML ファイルへのパス)
URL ルールで正規表現を使用するように記述したい場合は、 :regexp t
機能するはずです。
(defroute ("/hello/([w]+)" :regexp t) (&key キャプチャ) (形式 nil "Hello, ~A!" (最初のキャプチャ)))
通常、ルートは定義された順序で一致するかどうかテストされ、最初に一致したルートのみが呼び出され、後続のルートは無視されます。ただし、 next-route
を含めることにより、ルートはリスト内の一致のテストを続行できます。
(defroute "/guess/:who" (&key who) (if (string= who "栄太郎") 「わかったよ!」 (次ルート))) (defroute "/guess/*" () "見逃した!")
defroute
の結果として次の形式を返すことができます。
弦
パス名
Clack の応答リスト (ステータス、ヘッダー、本文を含む)
(redirect "url")
を使用して別のルートにリダイレクトします。 2 番目のオプションの引数はステータス コードで、デフォルトでは 302 です。
名前を使用してルートを定義した場合、 (url-for route-name &rest params)
を使用して名前から URL を検索できます。
ルートが見つからない場合、関数はエラーをスローします。
以下も参照してください。
add-query-parameters base-url params
角括弧 (「[」 & 「]」) を含むパラメータ キーは、構造化パラメータとして解析されます。ルーターでは、解析されたパラメーターに_parsed
としてアクセスできます。
<フォームアクション="/edit"> <input type="name" name="人[名前]" /> <input type="name" name="人[メールアドレス]" /> <input type="name" name="人[生年月日][年] ]" /> <input type="name" name="人[誕生日][月]" /> <input type="name" name="人[誕生日][日]" /></form>
(defroute "/edit" (&key _parsed) (format nil "~S" (cdr (assoc "person" _parsed :test #'string=))));=> "(("名前" . "英太郎") ("メール" . "e.arrows@gmail .com") ("誕生" . (("年" . 2000) ("月" . 1) ("日" . 1))))";; assoc-utils(ql:quickload :assoc-utils)を使用する場合 (インポート 'assoc-utils:aget) (defroute "/edit" (&key _parsed) (フォーマット nil "~S" (aget _parsed "person")))
空白のキーは、複数の値があることを意味します。
<フォームアクション="/add"> <input type="text" name="アイテム[][名前]" /> <input type="text" name="アイテム[][価格]" /> <input type="text" name="アイテム[ ][名前]" /> <input type="text" name="商品[][価格]" /> <input type="submit" value="追加" /></form>
(defroute "/add" (&key _parsed) (format nil "~S" (assoc "items" _parsed :test #'string=)));=> "((("名前" . "WiiU") ("価格" . "30000")) (("名前" . "PS4") ("価格" . "69000")))"
Caveman は、デフォルトのテンプレート エンジンとして Djula を使用します。
{% 「layouts/default.html」を拡張します %} {% ブロック タイトル %}ユーザー | MyApp{% エンドブロック %} {% ブロック コンテンツ %}<div id="main"> <ul> {% for user in users %}<li><a href="{{ user.url }}">{{ user.name }}</a></li> {% エンドフォー %} </ul></div>{% エンドブロック %}
(インポート 'myapp.view:render) (レンダリング #P"users.html"'(:users ((:url "/id/1" :name "nitro_idiot") (:url "/id/2" :name "meymao")) :has-next-page T))
データベースから何かを取得したり、Djula を使用して関数を実行したりする場合は、レンダリングする引数を渡すときに、コードが実行されるように明示的にlist
呼び出す必要があります。
(インポート 'myapp.view:render) (レンダリング #P"users.html"(list :users (get-users-from-db)))
これは JSON API の例です。
(defroute "/user.json" (&key |id|) (let ((person (find-person-from-db |id|)));; person => (:|name| "深町栄太郎" :|email| "[email protected]")(render- json person)));=> {"名前":"深町栄太郎","メール":"[email protected]"}
render-json
スケルトン プロジェクトの一部です。そのコードは「src/view.lisp」にあります。
デフォルトでは、「static/」ディレクトリ内の画像、CSS、JS、favicon.ico、robot.txt が提供されます。
/images/logo.png => {PROJECT_ROOT}/static/images/logo.png /css/main.css => {PROJECT_ROOT}/static/css/main.css /js/app/index.js => {PROJECT_ROOT}/static/js/app/index.js /robot.txt => {PROJECT_ROOT}/static/robot.txt /favicon.ico => {PROJECT_ROOT}/static/favicon.ico
これらのルールは「PROJECT_ROOT/app.lisp」を書き換えることで変更できます。詳細については、「Clack.Middleware.Static」を参照してください。
Caveman は構成スイッチャーとして Envy を採用しています。これにより、複数の構成を定義し、環境変数に応じてそれらを切り替えることができます。
これは典型的な例です。
(defpackage :myapp.config (:使用:cl:envy)) (パッケージ内:myapp.config) (setf (config-env-var) "APP_ENV") (defconfig :common `(:アプリケーションルート ,(asdf:コンポーネントパス名 (asdf:find-system :myapp)))) (defconfig |development| `(:debug T:databases((:maindb :sqlite3 :データベース名 ,(マージパス名 #P"test.db"*アプリケーション ルート*)))) (defconfig |production| '(:databases((:maindb :mysql :データベース名 "myapp" :ユーザー名 "whoami" :パスワード "1234") (:workerdb :mysql :データベース名 "jobs" :ユーザー名 "whoami" :パスワード "1234")))) (defconfig |staging| `(:debug T,@|production|))
すべての構成はプロパティ リストです。 APP_ENV
設定することで、使用する構成を選択できます。
現在の構成から値を取得するには、必要なキーを指定してmyapp.config:config
を呼び出します。
(インポート 'myapp.config:config) (setf (osicat:環境変数 "APP_ENV") "開発") (config :debug);=> T
:databases
構成に追加すると、Caveman はデータベースのサポートを有効にします。 :databases
データベース設定の関連付けリストです。
(defconfig |production| '(:databases((:maindb :mysql :データベース名 "myapp" :ユーザー名 "whoami" :パスワード "1234") (:workerdb :mysql :データベース名 "jobs" :ユーザー名 "whoami" :パスワード "1234"))))
myapp.db
パッケージ内のdb
は、上記で設定した各データベースに接続するための関数です。ここに一例を示します。
(パッケージを使用 '(:myapp.db :sxql :datafly)) (defun 検索-アダルト () (接続あり(db) (すべてを取得 (選択:*(:人から) (ここで (:>= :20 歳)))))
接続は Lisp セッション中はアクティブであり、すべての HTTP リクエストで再利用されます。
retrieve-all
とクエリ言語は datafly と SxQL から来ています。詳細については、これらのドキュメントのセットを参照してください。
HTTP リクエスト中に使用できる特別な変数がいくつかあります。 *request*
と*response*
リクエストとレスポンスを表します。 Clack に詳しい方にとっては、これらは Clack.Request と Clack.Response のサブクラスのインスタンスです。
(パッケージを使用:caveman2);; Referer ヘッダーの値を取得します。(http-referer *request*);; Content-Type ヘッダーを設定します。(setf (getf (response-headers *response*) :content-type) "application/json");; HTTP ステータスを設定します。(setf (status *response*) 304)
すべての「*.json」リクエストに対して Content-Type を「application/json」に設定したい場合は、 next-route
使用できます。
(defroute "/*.json" () (setf (getf (response-headers *response*) :content-type) "application/json") (次のルート)) (defroute "/user.json" () ...) (defroute "/search.json" () ...) (defroute ("/new.json" :method :POST) () ...)
セッションデータは、ユーザー固有のデータを記憶するためのものです。 *session*
セッション データを格納するハッシュ テーブルです。
この例では、セッション内の:counter
増分し、訪問者ごとにそれを表示します。
(「/counter」を無効にする() (フォーマット nil "あなたはここに ~A 回来ました。" (incf (gethash :counter *session* 0))))
Caveman2 は、デフォルトでセッション データをメモリ内に保存します。これを変更するには、「PROJECT_ROOT/app.lisp」の:session
に:store
指定します。
この例では、RDBMS を使用してセッション データを保存します。
'(:バックトレース :output (getf (config) :error-log)) nil)- :session+ (:session+ :store (make-dbi-store :connector (lambda ()+ (apply #'dbi:connect+ (myapp.db:connection-settings))))) (if (本番環境) なし (ラムダ(アプリ)
注: : :lack-session-store-dbi
アプリの:depends-on
として追加することを忘れないでください。これは、Clack/Lack の一部ではありません。
詳細については、Lack.Session.Store.DBi のソース コードを参照してください。
Lack.Session.Store.Dbi
(インポート 'caveman2:throw-code) (defroute ("/auth" :method :POST) (&key |name| |password|) ((許可 |名前| |パスワード|を除く) (スローコード 403)))
404、500 などのエラー ページを指定するには、アプリのon-exception
メソッドを定義します。
(defmethod 例外 ((app <web>) (コード (eql 404))) (宣言(アプリコードを無視)) (マージパス名 #P"_errors/404.html" *テンプレートディレクトリ*))
Caveman にはホット デプロイメントのための機能はありませんが、Server::Starter (Perl モジュール) を使用するとそれが簡単になります。
$ APP_ENV=production start_server --port 8080 -- clackup --server :fcgi app.lisp
注: Server::Starter では、サーバーが特定の fd へのバインディングをサポートする必要があります。つまり、 start_server
コマンドで動作するのは:fcgi
と:woo
だけです。
サーバーを再起動するには、HUP シグナル ( kill -HUP <pid>
) をstart_server
プロセスに送信します。
Caveman は、設定の:error-log
で指定されたファイルにエラー バックトレースを出力します。
(defconfig |default| `(:error-log #P"/var/log/apps/myapp_error.log":databases((:maindb :sqlite3 :データベース名 ,(マージパス名 #P"myapp.db"*アプリケーションルート*)))))
(インポート 'cl-who:with-html-output-to-string) (「/」を無視します() (with-html-output-to-string (出力 nil :prologue t) (:html (:head (:title "穴居人へようこそ!")) (:body "なんとかなんとか。"))));=> "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/ xhtml1/DTD/xhtml1-strict.dtd">; <html><head><title>Caveman へようこそ!</title></head><body>何とか何とかああ。</body></html>」
CL-WHOウェブサイト
(インポート 'cl-markup:xhtml) (「/」を無視します() (xhtml (:head (:title "穴居人へようこそ!")) (:body "何とか何とか。")));=> "<?xml version="1.0"coding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional/ /EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head><title>へようこそ穴居人!</title></head><body>何とか何とかなんとか。</body></html>"
CL-マークアップリポジトリ
{名前空間 myapp.view} {テンプレート renderIndex}<!DOCTYPE html><html><head> <title>「穴居人へようこそ!</title></head><body> なんとかなんとかなんとか。</body></html>{/template}
(インポート 'myapp.config:*テンプレート ディレクトリ*) (closure-template:compile-cl-templates (merge-pathnames #P"index.tmpl"*template-directory*)) (「/」を無視します() (myapp.view:render-index))
cl-クロージャ-テンプレート
クロージャテンプレートのドキュメント
Clack - Web アプリケーション環境。
欠如 - Clack の核心。
ningle - Caveman のベースとなっている超マイクロ Web アプリケーション フレームワーク。
Djula - HTML テンプレート エンジン。
CL-DBI - データベースに依存しないインターフェイス ライブラリ。
SxQL - SQL ビルダー ライブラリ。
Envy - 構成スイッチャー。
Roswell - Common Lisp 実装マネージャー。
深町栄太郎([email protected])
LLGPLライセンスに基づいてライセンスされています。