(defparameter *web* (make-instance '<app>)) @route GET“/”(defun索引() (渲染#P“index.tmpl”)) @route GET "/hello"(defun say-hello (&key (|name| "Guest")) (格式為零“你好,〜A”|名稱|))
一切。 Caveman2 是從頭開始寫的。
這些都是值得注意的點。
基於ningle
具有資料庫集成
使用新的獨立配置系統(Envy)
有新的路由宏
最常見的問題之一是“我應該使用哪一個:ningle 還是 Caveman?有什麼區別?”我認為這些問題被如此頻繁地問到是因為 Caveman 和 ningle 太相似了。它們都被稱為“micro”,並且沒有資料庫支援。
有了Caveman2,Caveman 不再是一個「微型」Web 應用程式框架。支援CL-DBI,預設具有資料庫連線管理。穴居人已經開始長大。
Caveman 旨在成為 Web 應用程式公共部分的集合。對於 Caveman2,我使用三個規則來做決定:
可擴展。
務實一點。
不要強迫任何事。
你來這裡是因為你有興趣像穴居人一樣生活,對嗎?這不是迪士尼樂園,但我們可以從這裡開始。我們進入一個山洞吧!
Caveman2 現已在 Quicklisp 上提供。
(ql:快速載入:穴居人2)
(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)
您的應用程式具有名為start
和stop
函數來啟動/停止您的 Web 應用程式。
(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 提供了兩種定義路由的方法 - @route
和defroute
。您可以使用其中任何一個。
@route
是一個註解宏,使用cl-annot定義。它需要一個方法、一個 URL 字串和一個函數。
@route GET“/”(defun索引() ...);;沒有名稱的路線。 (格式為零“歡迎,〜A”|名稱|))
除了其參數列表之外,這與 Caveman1 的@url
類似。當不需要時,您不必指定參數。
defroute
只是一個巨集。它提供與@route
相同的功能。
(defroute索引“/”() ...);;沒有名稱的路線。 (格式為零“歡迎,〜A”|名稱|))
由於Caveman是基於ningle,所以Caveman也有類似Sinatra的路由系統。
;; GET 請求(預設)@route GET "/" (lambda () ...) (defroute(“/”:方法:GET)()...);; POST 請求@路由 POST "/" (lambda () ...) (defroute(“/”:方法:POST)()...);; PUT 請求@路由 PUT "/" (lambda () ...) (defroute(“/”:方法:PUT)()...);;刪除請求@路由刪除“/”(lambda()...) (defroute(“/”:方法:刪除)()...);;選項 request@route 選項 "/" (lambda () ...) (defroute(“/”:方法:選項)()...);;對於所有方法@route ANY "/" (lambda () ...) (defroute(“/”:方法:ANY)()...)
路由模式可能包含“關鍵字”以將值放入參數中。
(defroute "/hello/:name" (&鍵名稱) (格式為零“你好,~A”名字))
當您存取“/hello/Eitaro”或“/hello/Tomohiro”時,將呼叫上述控制器, name
將根據情況為“Eitaro”或“Tomohiro”。
(&key name)
與 Common Lisp 的 lambda 列表幾乎相同,只是它總是允許其他鍵。
(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 (格式為零“〜A”splat)) ;=>(路徑/到/文件 xml)
如果您想在 URL 規則中使用正規表示式, :regexp t
應該可以。
(defroute ("/hello/([w]+)" :regexp t) (&key 捕獲) (格式為零“你好,〜A!”(第一次捕獲)))
通常,路由會依照定義的順序進行匹配測試,並且僅呼叫第一個符合的路由,而忽略後續路由。但是,路由可以透過包含next-route
來繼續測試清單中的匹配項。
(defroute "/guess/:who" (&key who) (if (string= who "Eitaro") "你抓住我了!" (next-route))) (defroute "/guess/*" () "你錯過了!")
您可以傳回以下格式作為defroute
的結果。
細繩
路徑名
Clack 的回覆清單(包含 Status、Headers 和 Body)
使用(redirect "url")
重定向到另一個路由。第二個可選參數是狀態代碼,預設為 302。
當您使用名稱定義路由時,您可以使用(url-for route-name &rest params)
從名稱中找到 URL。
如果沒有找到路由,該函數將拋出錯誤。
參見:
add-query-parameters base-url params
包含方括號(“[”和“]”)的參數鍵將被解析為結構化參數。您可以在路由器中以 _parsed _parsed
存取已解析的參數。
<表單操作=“/編輯”> <input type="name" name="person[name]" /> <input type="name" name="person[email]" /> <input type="name" name="person[birth][year ]" /> <input type="name" name="person[birth][month]" /> <input type="name" name="person[birth][day]" /></form>
(defroute“/edit”(&key_parsed) (format nil "~S" (cdr (assoc "person" _parsed :test #'string=))));=> "(("name" . "Eitaro") ("email" . "e.arrows@gmail .com") ("出生" . (("年" . 2000) ("月" . 1) ("日" . 1))))";;使用 assoc-utils(ql:quickload :assoc-utils) (導入'assoc-utils:aget) (defroute“/edit”(&key_parsed) (格式nil“~S”(aget_parsed“人”)))
空白鍵意味著它們有多個值。
<表單操作=“/新增”> <input type="text" name="items[][name]" /> <input type="text" name="items[][price]" /> <input type="text" name="items[ ][name]" /> <input type="text" name="items[][price]" /> <input type="submit" value="Add" /></form>
(defroute“/add”(&key_parsed) (format nil "~S" (assoc "items" _parsed :test #'string=)));=> "((("名稱" . "WiiU") ("價" . "30000")) (("名稱" . "PS4") ("價格" . "69000")))"
Caveman 使用 Djula 作為其預設模板引擎。
{% 擴充“layouts/default.html” %} {% 區塊標題 %}使用者 | MyApp{% endblock %} {% 區塊內容 %}<div id="main"> <ul> {% for users in users %}<li><a href="{{ user.url }}">{{ user.name }}</a></li> {% endfor %} </ul></div>{% endblock %}
(導入'myapp.view:渲染) (渲染 #P"users.html"'(:users ((:url "/id/1" :name "硝基白痴") (:url "/id/2" :name "meymao")) :有下一頁 T))
如果您想從資料庫取得某些內容或使用 Djula 執行函數,則必須在將參數傳遞給 render 時明確呼叫list
以便程式碼執行。
(導入'myapp.view:渲染) (渲染 #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| "Eitaro Fukamachi" :|email| "[email protected]")(render- json person)));=> {"name":"Eitaro Fukamachi","email":"[email protected]"}
render-json
是骨架專案的一部分。你可以在“src/view.lisp”中找到它的程式碼。
預設將提供“static/”目錄中的圖像、CSS、JS、favicon.ico 和 robots.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:羨慕)) (套件內:myapp.config) (setf(config-env-var)“APP_ENV”) (defconfig:通用 `(:應用程式根,(asdf:元件路徑名(asdf:find-system:myapp)))) (defconfig |development| `(:debug T:databases((:maindb :sqlite3 :database-name ,(merge-pathnames #P"test.db"*application-root*))))) (defconfig |生產| '(:資料庫((:maindb :mysql :資料庫名稱「myapp」:使用者名稱「whoami」:密碼「1234」) (:workerdb:mysql:資料庫名稱「jobs」:使用者名稱「whoami」:密碼「1234」))) (defconfig |staging| `(:debug T,@|生產|))
每個配置都是一個屬性清單。您可以透過設定APP_ENV
選擇要使用的配置。
若要從目前設定中取得值,請使用所需的金鑰呼叫myapp.config:config
。
(導入'myapp.config:設定) (setf(osicat:環境變數“APP_ENV”)“開發”) (配置:調試);=> T
當您將:databases
新增至設定時,Caveman 會啟用資料庫支援。 :databases
是資料庫設定的關聯列表。
(defconfig |生產| '(:資料庫((: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 header的值。設定 Content-Type 標頭。設定 HTTP 狀態。
如果您想要為所有“*.json”請求設定Content-Type“application/json”,則可以使用next-route
。
(defroute "/*.json" () (setf(getf(回應頭*回應*):內容類型)“application/json”) (下一條路線)) (defroute "/user.json" () ...) (defroute "/search.json" () ...) (defroute(“/new.json”:方法:POST)()...)
會話資料用於儲存使用者特定的資料。 *session*
是儲存會話資料的雜湊表。
此範例遞增會話中的:counter
,並為每個訪客顯示它。
(defroute "/counter" () (格式nil「你來過這裡~A次。」(incf (gethash :counter *session* 0))))
Caveman2 預設將會話資料儲存在記憶體中。若要變更此設置,請在「PROJECT_ROOT/app.lisp」中將:store
指定為:session
。
此範例使用 RDBMS 來儲存會話資料。
'(:回溯 :輸出(getf(配置):錯誤日誌)) nil)- :session+ (:session+ :store (make-dbi-store :connector (lambda ()+ (apply #'dbi:connect+ (myapp.db:connection-settings))))) (如果(生產p) 零 (拉姆達(應用程式)
注意:不要忘記添加:lack-session-store-dbi
作為:depends-on
您的應用程式。它不是 Clack/Lack 的一部分。
有關更多信息,請參閱 Lack.Session.Store.DBi 的源代碼。
缺Session.Store.Dbi
(導入'caveman2:拋出程式碼) (defroute ("/auth" :方法 :POST) (&key |名稱| |密碼|) (除非(授權 |名稱| |密碼|) (拋出代碼 403)))
若要指定 404、500 等錯誤頁面,請定義應用程式的on-exception
方法。
(defmethod on-exception ((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,這表示只有:fcgi
和:woo
可以與start_server
指令一起使用。
若要重新啟動伺服器,請將 HUP 訊號 ( kill -HUP <pid>
) 傳送至start_server
程序。
Caveman 將錯誤回溯輸出到設定中的:error-log
指定的檔案中。
(defconfig |default| `(:error-log #P"/var/log/apps/myapp_error.log":databases((:maindb :sqlite3 :database-name ,(merge-pathnames #P"myapp.db"*應用程式根*)))))
(導入 'cl-who:with-html-output-to-string) (預設路由“/”() (with-html-output-to-string (輸出 nil :prologue t) (:html (:head (:title "歡迎來到穴居人!")) (:body "Blah blah blah."))));=> "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict //EN" "http://www.w3.org/TR/ xhtml1/DTD/xhtml1-strict.dtd">; <html><head><title>歡迎來到穴居人!</title></head><body>Blah blah blah.</body></html> "
CL-世界衛生組織網站
(導入'cl-標記:xhtml) (預設路由“/”() (xhtml (:head (:title 「歡迎來到穴居人!」))) (:body "Blah blah blah.")));=> "<?xml version="1.0"encoding="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> <身體 >巴拉巴拉巴拉。
CL-標記存儲庫
{命名空間 myapp.view} {模板 renderIndex}<!DOCTYPE html><html><head> <title>「歡迎來到穴居人!</title></head><body> 等等等等。
(導入'myapp.config:*模板目錄*) (closure-template:compile-cl-templates (merge-pathnames #P"index.tmpl"*template-directory*)) (預設路由“/”() (myapp.view:渲染索引))
cl 閉合模板
閉包模板文檔
Clack - Web 應用程式環境。
Lack - Clack 的核心。
ningle - Caveman 所基於的超微型 Web 應用程式框架。
Djula - HTML 模板引擎。
CL-DBI - 獨立於資料庫的介面庫。
SxQL - SQL 建構器函式庫。
Envy - 配置切換器。
Roswell - Common Lisp 實施經理。
深町榮太郎 ([email protected])
根據 LLGPL 許可證獲得許可。