(defparameter *web* (إنشاء مثيل '<app>)) @route GET "/"(مؤشر إلغاء التشغيل () (عرض #P"index.tmpl")) @route GET "/hello"(defun say-hello (&key (|name| "Guest")) (تنسيق لا شيء "Hello, ~A" |الاسم|))
كل شئ. تمت كتابة Caveman2 من الصفر.
وهذه نقاط جديرة بالملاحظة.
يعتمد على نينجل
لديه تكامل قاعدة البيانات
يستخدم نظام تكوين منفصل جديد (الحسد)
يحتوي على ماكرو توجيه جديد
أحد الأسئلة الأكثر شيوعًا هو "أيهما يجب أن أستخدم: نينجل أم رجل الكهف؟ ما هي الاختلافات؟" أعتقد أن هذه الأسئلة تم طرحها بشكل متكرر لأن رجل الكهف ونيجل كانا متشابهين للغاية. كلاهما يُطلق عليهما اسم "micro"، ولا يوجد لديهما دعم لقاعدة البيانات.
مع Caveman2، لم يعد Caveman عبارة عن إطار عمل لتطبيق ويب "صغير". وهو يدعم CL-DBI، ولديه إدارة اتصال قاعدة البيانات بشكل افتراضي. لقد بدأ رجل الكهف يكبر.
يهدف Caveman إلى أن يكون عبارة عن مجموعة من الأجزاء الشائعة لتطبيقات الويب. مع Caveman2، أستخدم ثلاث قواعد لاتخاذ القرارات:
تكون قابلة للتوسيع.
كن عمليًا.
لا تجبر أي شيء.
أتيت إلى هنا لأنك مهتم بالعيش كرجل الكهف، أليس كذلك؟ هذه ليست ديزني لاند، ولكن يمكننا أن نبدأ هنا. دعونا ندخل إلى الكهف!
Caveman2 متاح الآن على Quicklisp.
(قل: التحميل السريع : رجل الكهف 2)
(caveman2:make-project #P"/path/to/myapp/" :author "<Your full name>");-> Writing /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". قبل بدء تشغيل الخادم، يجب عليك أولاً تحميل التطبيق الخاص بك.
(قل: التحميل السريع: myapp)
يحتوي التطبيق الخاص بك على وظائف تسمى 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 طريقتين لتحديد المسار - @route
و defroute
. يمكنك استخدام أي منهما.
@route
هو ماكرو للتعليق التوضيحي، يتم تعريفه باستخدام cl-annot. يستغرق الأمر طريقة وسلسلة URL ووظيفة.
@route GET "/"(مؤشر إلغاء التشغيل () ...)؛؛ مسار بدون اسم.@route GET "/welcome"(lambda (&مفتاح (|اسم| "ضيف")) (تنسيق لا شيء "مرحبًا، ~A" |الاسم|))
وهذا مشابه لـ @url
الخاص بـ Caveman1 باستثناء قائمة الوسيطات الخاصة به. ليس عليك تحديد وسيطة عندما لا تكون مطلوبة.
defroute
هو مجرد ماكرو. وهو يوفر نفس وظيفة @route
.
(مؤشر ديفروت "/" () ...)؛؛ مسار بدون اسم.(defroute "/welcome" (&key (|name| "Guest")) (تنسيق لا شيء "مرحبًا، ~A" |الاسم|))
نظرًا لأن رجل الكهف يعتمد على نينجل، فإن رجل الكهف لديه أيضًا نظام توجيه يشبه سيناترا.
؛؛ الحصول على طلب (افتراضي)@route الحصول على "/" (lambda () ...) (ديفروت ("/" :method :GET) () ...)؛؛ POST request@route POST "/" (lambda () ...) (ديفروت ("/" :method :POST) () ...)؛؛ ضع طلب @ الطريق ضع "/" (لامدا () ...) (ديفروت ("/" :الطريقة :PUT) () ...)؛؛ حذف request@route حذف "/" (لامدا () ...) (ديفروت ("/" :الطريقة :DELETE) () ...)؛؛ خيارات request@route خيارات "/" (لامدا () ...) (ديفروت ("/" :method :OPTIONS) () ...)؛؛ لجميع الطرق@route ANY "/" (lambda () ...) (ديفروت ("/" :الطريقة: أي) () ...)
قد تحتوي أنماط المسار على "كلمات رئيسية" لوضع القيمة في الوسيطة.
(defroute "/hello/:name" (&اسم المفتاح) (التنسيق لا شيء "Hello, ~A")
سيتم استدعاء وحدة التحكم المذكورة أعلاه عند الوصول إلى "/hello/Eitaro" أو "/hello/Tomohiro"، وسيكون name
"Eitaro" أو "Tomohiro"، حسب الاقتضاء.
(&key name)
يشبه تقريبًا قائمة lambda في Common Lisp، إلا أنه يسمح دائمًا بمفاتيح أخرى.
(defroute "/hello/:name" (&rest params &key name) ؛؛ ... )
قد تحتوي أنماط المسار أيضًا على معلمات "أحرف البدل". يمكن الوصول إليها باستخدام splat
.
(defroute "/say/*/to/*" (&key splat) ؛ يطابق /say/hello/to/world (تنسيق nil "~A" splat));=> (hello World)(defroute "/download/*.*" (&key splat) ; match /download/path/to/file.xml (تنسيق لا شيء "~A" splat)) ;=> (المسار/إلى/ملف XML)
إذا كنت ترغب في الكتابة، استخدم تعبيرًا عاديًا في قاعدة عنوان URL، فيجب أن يعمل :regexp t
.
(defroute ("/hello/([w]+)" :regexp t) (&التقاط المفاتيح) (تنسيق لا شيء "Hello, ~A!" (اللقطات الأولى)))
عادةً، يتم اختبار المسارات للتأكد من تطابقها بالترتيب الذي تم تعريفها به، ويتم استدعاء المسار الأول المطابق فقط، مع تجاهل المسارات التالية. ومع ذلك، يمكن أن يستمر المسار في اختبار التطابقات في القائمة، من خلال تضمين next-route
.
(defroute "/ خمن /: من" (&مفتاح من) (if (string= who "Eitaro") "لقد حصلت علي!" (المسار التالي))) (defroute "/guess/*" () "لقد فاتك!")
يمكنك إرجاع التنسيقات التالية كنتيجة لـ defroute
.
خيط
اسم المسار
قائمة ردود Clack (تحتوي على الحالة والرؤوس والنص)
إعادة التوجيه إلى مسار آخر باستخدام (redirect "url")
. الوسيطة الاختيارية الثانية هي رمز الحالة، 302 افتراضيًا.
عندما تحدد المسارات بأسماء، يمكنك العثور على عنوان URL من الاسم باستخدام (url-for route-name &rest params)
.
ستلقي الوظيفة خطأ إذا لم يتم العثور على مسار.
أنظر أيضا:
add-query-parameters base-url params
سيتم تحليل مفاتيح المعلمات التي تحتوي على أقواس مربعة ("[" & "]") كمعلمات منظمة. يمكنك الوصول إلى المعلمات التي تم تحليلها على أنها _parsed
في أجهزة التوجيه.
<إجراء النموذج = "/ تحرير"> <نوع الإدخال = "اسم" اسم = "شخص [اسم]" /> <نوع الإدخال = "اسم" اسم = "شخص [بريد إلكتروني]" /> <نوع إدخال = "اسم" اسم = "شخص [ولادة] [سنة] ]" /> <input type="name" name="person[birth][month]" /> <input type="name" name="person[birth][day]" /></form>
(ديفروت "/ تحرير" (&key _parsed) (تنسيق nil "~S" (cdr (assoc "person" _parsed :test #'string=))));=> "(("name" . "Eitaro") ("email" . "e.arrows@gmail .com") ("الولادة" . (("السنة" . 2000) ("الشهر" . 1) ("اليوم" . 1))))"؛؛ مع مساعد-utils(ql: التحميل السريع: مساعد-utils) (استيراد 'assoc-utils:aget) (ديفروت "/ تحرير" (&key _parsed) (تنسيق لا شيء "~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>
(ديفروت "/ إضافة" (&key _parsed) (تنسيق nil "~S" (assoc "items" _parsed :test #'string=)));=> "((("name" . "WiiU") ("price" . "30000")) ((" الاسم" . "PS4") ("السعر" . "69000")))"
يستخدم Caveman Djula كمحرك القوالب الافتراضي.
{% يمتد "layouts/default.html" %} {% منع العنوان %}المستخدمون | MyApp{% endblock %} {% حظر المحتوى %}<div id="main"> <ul> {% للمستخدم في المستخدمين %}<li><a href="{{ user.url }}">{{ user.name }}</a></li> {% endfor %} </ul></div>{% endblock %}
(استيراد '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.
(احذف "/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".
سيتم عرض الصور وCSS وJS وfavicon.ico وrobot.txt في الدليل "static/" بشكل افتراضي.
/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 لمزيد من التفاصيل.
رجل الكهف يتبنى الحسد كمحول التكوين. وهذا يسمح بتعريف تكوينات متعددة والتبديل بينها وفقًا لمتغير البيئة.
وهذا مثال نموذجي:
(إلغاء الحزمة :myapp.config (:استخدام :cl:الحسد)) (داخل الحزمة: myapp.config) (setf (config-env-var) "APP_ENV") (defconfig :common `(:جذر التطبيق،(asdf:اسم مسار المكون (asdf:find-system :myapp)))) (defconfig |development| `(:debug T:databases((:maindb :sqlite3 :database-name ,(merge-pathnames #P"test.db"*application-root*)))))) (defconfig |production| '(:databases((:maindb :mysql :database-name "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") "التطوير") (التكوين: تصحيح)؛=> T
عند إضافة :databases
إلى التكوين، يقوم Caveman بتمكين دعم قاعدة البيانات. :databases
هي قائمة اقترانات لإعدادات قاعدة البيانات.
(defconfig |production| '(:databases((:maindb :mysql :database-name "myapp" :اسم المستخدم "whoami" :كلمة المرور "1234") (:workerdb :mysql :اسم قاعدة البيانات "jobs" :اسم المستخدم "whoami" :كلمة المرور "1234"))))
db
في الحزمة myapp.db
هي وظيفة للاتصال بكل قاعدة بيانات تم تكوينها أعلاه. هنا مثال.
(استخدام الحزمة '(:myapp.db :sxql :datafly)) (إلغاء البحث عن البالغين () (مع اتصال (ديسيبل) (استرجاع الكل (اختر :*(من :شخص) (حيث (:>= :العمر 20))))))
يكون الاتصال حيًا أثناء جلسة Lisp، وسيتم إعادة استخدامه في كل طلب HTTP.
retrieve-all
ولغة الاستعلام جاءت من datafly وSxQL. راجع مجموعات الوثائق هذه لمزيد من المعلومات.
هناك العديد من المتغيرات الخاصة المتاحة أثناء طلب HTTP. *request*
و *response*
تمثل طلبًا واستجابة. إذا كنت معتادًا على Clack، فهذه أمثلة للفئات الفرعية لـ Clack.Request وClack.Response.
(حزمة الاستخدام :رجل الكهف2)؛؛ احصل على قيمة رأس المُحيل.(http-referer *request*);; تعيين رأس نوع المحتوى.(setf (getf (response-headers *response*) :content-type) "application/json");; اضبط حالة HTTP.(setf (الحالة *الاستجابة*) 304)
إذا كنت ترغب في تعيين نوع المحتوى "application/json" لجميع طلبات "*.json"، فيمكن استخدام next-route
.
(ديفروت "/*.json" () (setf (getf (رؤوس الاستجابة * الاستجابة *) : نوع المحتوى) "application/json") (المسار التالي)) (ديفروت "/user.json" () ...) (ديفروت "/search.json" () ...) (ديفروت ("/new.json" :method :POST) () ...)
بيانات الجلسة مخصصة لحفظ البيانات الخاصة بالمستخدم. *session*
عبارة عن جدول تجزئة يقوم بتخزين بيانات الجلسة.
يعمل هذا المثال على زيادة :counter
في الجلسة، ويعرضه لكل زائر.
(ديفروت "/ عداد" () (تنسيق nil "لقد أتيت إلى هنا ~ مرات." (incf (gethash :counter *session* 0))))
يقوم Caveman2 بتخزين بيانات الجلسة في الذاكرة بشكل افتراضي. لتغيير ذلك، حدد :store
إلى :session
في "PROJECT_ROOT/app.lisp".
يستخدم هذا المثال RDBMS لتخزين بيانات الجلسة.
'(:التتبع الخلفي : الإخراج (getf (التكوين): سجل الأخطاء)) لا شيء)- :session+ (:session+ :store (make-dbi-store :connector (lambda ()+ (apply #'dbi:connect+ (myapp.db:connection-settings)))) (إذا (الإنتاج ص) لا شيء (لامدا (التطبيق)
ملحوظة: لا تنس إضافة :lack-session-store-dbi
كـ :depends-on
لتطبيقك. إنه ليس جزءًا من Clack/Lack.
راجع الكود المصدري لـ Lack.Session.Store.DBi لمزيد من المعلومات.
Lack.Session.Store.Dbi
(استيراد "رجل الكهف 2: رمز الرمي") (defroute ("/auth" :method :POST) (&key |name| |password|) (ما لم (تفويض |الاسم| |كلمة المرور|) (رمز الرمي 403)))
لتحديد صفحات الخطأ لـ 404 أو 500 أو ما شابه، حدد طريقة on-exception
لتطبيقك.
(الطريقة الافتراضية للاستثناء ((التطبيق <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 (output 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>مرحبًا بك في رجل الكهف!</title></head><body>بلا بلاه بلاه.</body></html>"
موقع CL-WHO
(استيراد 'cl-markup: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><body>بلا بلا بلا.</body></html>"
مستودع CL-Markup
{مساحة الاسم myapp.view} {قالب renderIndex<!DOCTYPE html><html><head> <title>"مرحبًا بك في رجل الكهف!</title></head><body> بلا بلا بلا.</body></html>{/template}
(استيراد 'myapp.config:*دليل القالب*) (قالب الإغلاق: قوالب ترجمة cl (أسماء مسارات الدمج #P"index.tmpl"*دليل القالب*)) (ديفروت "/" () (myapp.view:render-index))
cl-إغلاق القالب
توثيق قوالب الإغلاق
Clack - بيئة تطبيقات الويب.
النقص - جوهر كلاك.
ningle - إطار تطبيق ويب فائق الصغر يعتمد عليه Caveman.
Djula - محرك HTML Templating.
CL-DBI - مكتبة واجهة مستقلة عن قاعدة البيانات.
SxQL - مكتبة منشئ SQL.
الحسد - مبدل التكوين.
روزويل - مدير تنفيذ Common Lisp.
إيتارو فوكاماتشي ([email protected])
مرخص بموجب ترخيص LLGPL.