(defparameter *web* (make-instance '<aplikasi>)) @route DAPATKAN "/"(defun indeks () (render #P"index.tmpl")) @route GET "/hello"(defun say-hello (&key (|nama| "Tamu")) (format nihil "Halo, ~A" |nama|))
Semuanya. Caveman2 ditulis dari awal.
Ini adalah poin-poin penting.
Berdasarkan ningle
Memiliki integrasi basis data
Menggunakan sistem konfigurasi baru yang terpisah (Envy)
Memiliki makro perutean baru
Salah satu pertanyaan yang paling sering diajukan adalah "Mana yang harus saya gunakan: ningle atau Caveman? Apa perbedaannya?" Saya rasa ini sering ditanyakan karena Caveman dan ningle terlalu mirip. Keduanya disebut "mikro", dan tidak memiliki dukungan database.
Dengan Caveman2, Caveman bukan lagi kerangka aplikasi web "mikro". Ini mendukung CL-DBI, dan memiliki manajemen koneksi database secara default. Manusia gua sudah mulai tumbuh dewasa.
Caveman dimaksudkan sebagai kumpulan bagian umum aplikasi web. Dengan Caveman2, saya menggunakan tiga aturan untuk mengambil keputusan:
Dapat diperluas.
Bersikaplah praktis.
Jangan memaksakan apapun.
Anda datang ke sini karena tertarik hidup seperti manusia gua, bukan? Ini bukan Disneyland, tapi kita bisa mulai dari sini. Ayo masuk ke dalam gua!
Caveman2 sekarang tersedia di Quicklisp.
(ql:muat cepat :manusia gua2)
(caveman2:make-project #P"/path/to/myapp/" :author "<Nama lengkap Anda>");-> menulis /path/to/myapp/.gitignore; menulis /path/to/myapp/README.markdown; menulis /path/to/myapp/app.lisp; menulis /path/to/myapp/db/schema.sql; menulis /path/to/myapp/shlyfile.lisp; menulis /path/to/myapp/myapp-test.asd; menulis /path/to/myapp/myapp.asd; menulis /path/to/myapp/src/config.lisp; menulis /path/to/myapp/src/db.lisp; menulis /path/to/myapp/src/main.lisp; menulis /path/to/myapp/src/view.lisp; menulis /path/to/myapp/src/web.lisp; menulis /path/to/myapp/static/css/main.css; menulis /path/to/myapp/t/myapp.lisp; menulis /path/to/myapp/templates/_errors/404.html; menulis /path/to/myapp/templates/index.tmpl; menulis /path/to/myapp/templates/layout/default.tmpl
Ini adalah contoh yang mengasumsikan bahwa nama aplikasi Anda adalah "myapp". Sebelum memulai server, Anda harus memuat aplikasi Anda terlebih dahulu.
(ql:muat cepat :aplikasi saya)
Aplikasi Anda memiliki fungsi bernama start
dan stop
untuk memulai/menghentikan aplikasi web Anda.
(aplikasi saya:mulai :port 8080)
Karena Caveman didasarkan pada Clack/Lack, Anda dapat memilih server mana yang akan dijalankan -- Hunchentoot, Woo atau Wookie, dll.
(aplikasi saya:mulai :server :hunchentoot :port 8080) (aplikasi saya:mulai :server :fcgi :port 8080)
Saya sarankan Anda menggunakan Hunchentoot di mesin lokal, dan menggunakan Woo di lingkungan produksi.
Anda juga dapat memulai aplikasi Anda dengan menggunakan perintah clackup.
$ ros install clack $ which clackup /Users/nitro_idiot/.roswell/bin/clackup $ APP_ENV=development clackup --server :fcgi --port 8080 app.lisp
Caveman2 menyediakan 2 cara untuk menentukan rute -- @route
dan defroute
. Anda dapat menggunakan keduanya.
@route
adalah makro anotasi, ditentukan dengan menggunakan cl-annot. Dibutuhkan metode, string URL, dan fungsi.
@route DAPATKAN "/"(defun indeks () ...);; Rute tanpa nama.@route GET "/welcome"(lambda (&key (|name| "Guest")) (format nihil "Selamat datang, ~A" |nama|))
Ini mirip dengan @url
Caveman1 kecuali daftar argumennya. Anda tidak perlu menentukan argumen jika tidak diperlukan.
defroute
hanyalah makro. Ini menyediakan fungsionalitas yang sama dengan @route
.
(menghilangkan indeks "/" () ...);; Rute tanpa nama.(defroute "/welcome" (&key (|name| "Guest")) (format nihil "Selamat datang, ~A" |nama|))
Karena Caveman berbasis pada ningle, Caveman juga memiliki sistem routing mirip Sinatra.
;; DAPATKAN permintaan (default)@route GET "/" (lambda () ...) (defroute ("/" :metode :GET) () ...);; POST permintaan@rute POST "/" (lambda () ...) (defroute ("/" :metode :POST) () ...);; PUT permintaan@route PUT "/" (lambda () ...) (defroute ("/" :metode :PUT) () ...);; HAPUS permintaan@route HAPUS "/" (lambda () ...) (defroute ("/" :metode :HAPUS) () ...);; OPSI request@route OPSI "/" (lambda() ...) (defroute ("/" :metode :OPTIONS) () ...);; Untuk semua metode@route APAPUN "/" (lambda () ...) (defroute ("/" :metode :APA PUN) () ...)
Pola rute mungkin berisi "kata kunci" untuk memasukkan nilai ke dalam argumen.
(menghilangkan "/hello/:name" (&nama kunci) (format nihil "Halo, ~A" nama))
Pengontrol di atas akan dipanggil ketika Anda mengakses "/hello/Eitaro" atau "/hello/Tomohiro", dan name
akan menjadi "Eitaro" atau "Tomohiro", sesuai kebutuhan.
(&key name)
hampir sama dengan daftar lambda dari Common Lisp, kecuali selalu mengizinkan kunci lain.
(menghilangkan "/hello/:name" (&rest params &nama kunci) ;; ... )
Pola rute mungkin juga berisi parameter "wildcard". Mereka dapat diakses dengan menggunakan splat
.
(defroute "/say/*/to/*" (&key splat); cocok dengan /say/hello/to/world (format nil "~A" splat));=> (hello world)(defroute "/download/*.*" (&key splat) ; cocok dengan /download/path/to/file.xml (format nihil percikan "~A")) ;=> (jalur/ke/file xml)
Jika Anda ingin menulis menggunakan ekspresi reguler dalam aturan URL, :regexp t
akan berfungsi.
(defroute ("/hello/([w]+)" :regexp t) (&pengambilan kunci) (format nil "Halo, ~A!" (pengambilan gambar pertama)))
Biasanya, rute diuji kecocokannya sesuai urutan yang ditentukan, dan hanya rute pertama yang cocok yang dipanggil, dengan rute berikut diabaikan. Namun, rute dapat terus menguji kecocokan dalam daftar, dengan menyertakan next-route
.
(menghilangkan "/tebak/:siapa" (&kunci siapa) (jika (string= siapa "Eitaro") "Kamu menangkapku!" (rute selanjutnya))) (menghilangkan "/ tebak/*" () "Kamu meleset!")
Anda dapat mengembalikan format berikut sebagai hasil defroute
.
Rangkaian
Nama jalur
Daftar respons Clack (berisi Status, Header, dan Isi)
Redirect ke rute lain dengan (redirect "url")
. Argumen opsional kedua adalah kode status, 302 secara default.
Saat Anda menentukan rute dengan nama, Anda dapat menemukan URL dari nama dengan (url-for route-name &rest params)
.
Fungsi ini akan memunculkan kesalahan jika tidak ada rute yang ditemukan.
Lihat juga:
add-query-parameters base-url params
Kunci parameter yang berisi tanda kurung siku ("[" & "]") akan diurai sebagai parameter terstruktur. Anda dapat mengakses parameter yang diurai sebagai _parsed
di router.
<bentuk tindakan="/edit"> <input type="nama" nama="orang[nama]" /> <input type="nama" nama="orang[email]" /> <input type="nama" name="orang[lahir][tahun ]" /> <input type="name" name="orang[kelahiran][bulan]" /> <input type="name" name="orang[kelahiran][hari]" /></form>
(menghilangkan "/edit" (&kunci _diurai) (format nil "~S" (cdr (assoc "person" _parsed :test #'string=))));=> "(("name" . "Eitaro") ("email" . "e.arrows@gmail .com") ("kelahiran" .(("tahun" .2000) ("bulan" .1) ("hari" .1)))));; Dengan assoc-utils(ql:quickload :assoc-utils) (impor 'assoc-utils:aget) (menghilangkan "/edit" (&kunci _diurai) (format nil "~S" (aget _parsed "orang")))
Kunci kosong berarti memiliki banyak nilai.
<bentuk tindakan="/tambahkan"> <input type="teks" nama="barang[][nama]" /> <input type="teks" nama="barang[][harga]" /> <input type="teks" nama="barang[ ][nama]" /> <input type="text" name="item[][harga]" /> <input type="submit" value="Tambahkan" /></form>
(menghilangkan "/tambahkan" (&kunci _diurai) (format nil "~S" (assoc "items" _parsed :test #'string=)));=> "((("name" . "WiiU") ("price" . "30000")) ((" nama". "PS4") ("harga". "69000")))"
Caveman menggunakan Djula sebagai mesin templating defaultnya.
{% memperluas "layouts/default.html" %} {% judul blok %}Pengguna | Aplikasi Saya{% blok akhir %} {% memblokir konten %}<div id="main"> <ul> {% untuk pengguna di pengguna %}<li><a href="{{ user.url }}">{{ user.name }}</a></li> {% endfor %} </ul></div>{% endblock %}
(impor 'myapp.view:render) (render #P"users.html"'(:users ((:url "/id/1" :nama "nitro_idiot") (:url "/id/2" :nama "meymao")) :memiliki halaman berikutnya T))
Jika Anda ingin mendapatkan sesuatu dari database atau menjalankan fungsi menggunakan Djula, Anda harus menjelaskan list
panggilan secara eksplisit saat meneruskan argumen untuk dirender agar kode dapat dieksekusi.
(impor 'myapp.view:render) (render #P"users.html"(daftar :pengguna (dapatkan-pengguna-dari-db)))
Ini adalah contoh API JSON.
(menghilangkan "/user.json" (&kunci |id|) (biarkan ((orang (temukan-orang-dari-db |id|)));; orang => (:|nama| "Eitaro Fukamachi" :|email| "[email protected]")(render- json person)));=> {"name":"Eitaro Fukamachi","email":"[email protected]"}
render-json
adalah bagian dari proyek kerangka. Anda dapat menemukan kodenya di "src/view.lisp".
Gambar, CSS, JS, favicon.ico dan robot.txt di direktori "statis/" akan disajikan secara default.
/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
Anda dapat mengubah aturan ini dengan menulis ulang "PROJECT_ROOT/app.lisp". Lihat Clack.Middleware.Static untuk detailnya.
Caveman mengadopsi Envy sebagai pengalih konfigurasi. Hal ini memungkinkan definisi beberapa konfigurasi dan peralihan di antara konfigurasi tersebut berdasarkan variabel lingkungan.
Ini adalah contoh tipikal:
(defpackage : aplikasi saya.config (:gunakan :cl:iri)) (dalam paket :myapp.config) (setf (config-env-var) "APP_ENV") (defconfig :umum `(:akar-aplikasi ,(asdf:nama jalur-komponen (asdf:temukan-sistem :aplikasi saya)))) (defconfig |pengembangan| `(:debug T:databases((:maindb :sqlite3 :nama-database ,(nama jalur gabungan #P"test.db"*root-aplikasi*))))) (defconfig |produksi| '(:databases((:maindb :mysql :nama-database "aplikasi saya" :nama pengguna "whoami" :kata sandi "1234") (:workerdb :mysql :nama-database "pekerjaan" :nama pengguna "whoami" :kata sandi "1234")))) (defconfig |pementasan| `(:debug T,@|produksi|))
Setiap konfigurasi adalah daftar properti. Anda dapat memilih konfigurasi yang akan digunakan dengan mengatur APP_ENV
.
Untuk mendapatkan nilai dari konfigurasi saat ini, panggil myapp.config:config
dengan kunci yang Anda inginkan.
(impor 'myapp.config:config) (setf (osicat:variabel lingkungan "APP_ENV") "pengembangan") (konfigurasi :debug);=> T
Saat Anda menambahkan :databases
ke konfigurasi, Caveman mengaktifkan dukungan database. :databases
adalah daftar asosiasi pengaturan database.
(defconfig |produksi| '(:databases((:maindb :mysql :nama-database "aplikasi saya" :nama pengguna "whoami" :kata sandi "1234") (:workerdb :mysql :nama-database "pekerjaan" :nama pengguna "whoami" :kata sandi "1234"))))
db
dalam paket myapp.db
adalah fungsi untuk menghubungkan ke setiap database yang dikonfigurasi di atas. Ini sebuah contoh.
(paket penggunaan '(:myapp.db :sxql :datafly)) (defun pencarian-dewasa () (dengan koneksi (db) (ambil-semua (pilih :*(dari :orang) (di mana (:>= :usia 20))))))
Koneksi aktif selama sesi Lisp, dan akan digunakan kembali di setiap permintaan HTTP.
retrieve-all
dan bahasa kueri berasal dari datafly dan SxQL. Lihat kumpulan dokumentasi tersebut untuk informasi lebih lanjut.
Ada beberapa variabel khusus yang tersedia selama permintaan HTTP. *request*
dan *response*
mewakili permintaan dan respons. Jika Anda familiar dengan Clack, ini adalah contoh subkelas Clack.Request dan Clack.Response.
(paket penggunaan :manusia gua2);; Dapatkan nilai Referer header.(http-referer *request*);; Setel header Tipe Konten.(setf (getf (header respons *respons*) :tipe konten) "application/json");; Setel status HTTP.(setf (status *respons*) 304)
Jika Anda ingin menyetel Tipe Konten "application/json" untuk semua permintaan "*.json", next-route
dapat digunakan.
(menghilangkan "/*.json" () (setf (getf (header respons *respons*):tipe konten) "application/json") (rute berikutnya)) (menghilangkan "/user.json" () ...) (menghilangkan "/search.json" () ...) (defroute ("/new.json" :metode :POST) () ...)
Data sesi adalah untuk menghafal data spesifik pengguna. *session*
adalah tabel hash yang menyimpan data sesi.
Contoh ini menambah :counter
dalam sesi, dan menampilkannya untuk setiap pengunjung.
(menghilangkan "/ counter" () (format nihil "Anda datang ke sini ~A kali." (incf (gethash :counter *sesi* 0))))
Caveman2 menyimpan data sesi dalam memori secara default. Untuk mengubahnya, tentukan :store
ke :session
di "PROJECT_ROOT/app.lisp".
Contoh ini menggunakan RDBMS untuk menyimpan data sesi.
'(: penelusuran balik :output (getf (config) :log kesalahan)) nihil)- :sesi+ (:sesi+ :store (make-dbi-store :connector (lambda ()+ (terapkan #'dbi:connect+ (myapp.db:pengaturan koneksi))))) (jika (produksip) nol (lambda (aplikasi)
CATATAN: Jangan lupa menambahkan :lack-session-store-dbi
sebagai :depends-on
pada aplikasi Anda. Ini bukan bagian dari Clack/Lack.
Lihat kode sumber Lack.Session.Store.DBi untuk informasi lebih lanjut.
Kekurangan.Sesi.Store.Dbi
(impor 'manusia gua2: kode lempar) (defroute ("/auth" :metode :POST) (&kunci |nama| |kata sandi|) (kecuali (otorisasi |nama| |kata sandi|) (kode lempar 403)))
Untuk menentukan halaman kesalahan untuk 404, 500 atau semacamnya, tentukan metode on-exception
pada aplikasi Anda.
(pengecualian metode defef ((aplikasi <web>) (kode (eql 404))) (deklarasikan (abaikan kode aplikasi)) (gabungan nama jalur #P"_errors/404.html" *direktori-templat*))
Meskipun Caveman tidak memiliki fitur untuk penerapan panas, Server::Starter -- modul Perl -- membuatnya mudah.
$ APP_ENV=production start_server --port 8080 -- clackup --server :fcgi app.lisp
CATATAN: Server::Starter memerlukan server untuk mendukung pengikatan pada fd tertentu, yang berarti hanya :fcgi
dan :woo
yang berfungsi dengan perintah start_server
.
Untuk me-restart server, kirim sinyal HUP ( kill -HUP <pid>
) ke proses start_server
.
Caveman mengeluarkan penelusuran balik kesalahan ke file yang ditentukan di :error-log
dalam konfigurasi Anda.
(defconfig |default| `(:error-log #P"/var/log/apps/myapp_error.log":databases((:maindb :sqlite3 :nama-database ,(nama jalur gabungan #P"myapp.db"* root-aplikasi*)))))
(impor 'cl-who:dengan-html-output-ke-string) (menghilangkan "/" () (dengan-html-output-to-string (output nihil :prolog t) (:html (:head (:title "Selamat datang di Manusia Gua!")) (:body "Blah bla bla."))));=> "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/ xhtml1/DTD/xhtml1-strict.dtd">; <html><head><title>Selamat datang di Manusia Gua!</title></head><body>Blah bla bla.</body></html>"
Situs Web CL-WHO
(impor 'cl-markup:xhtml) (menghilangkan "/" () (xhtml (:head (:title "Selamat datang di Manusia Gua!")) (:body "Blah bla bla.")));=> "<?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>Selamat datang di Manusia Gua!</title></head><body>Blah bla bla.</body></html>"
Repositori CL-Markup
{namespace aplikasi saya.view} {templat renderIndex<!DOCTYPE html><html><head> <title>"Selamat datang di Manusia Gua!</title></head><body> Bla bla bla.</body></html>{/template}
(impor 'myapp.config:*template-directory*) (templat-penutupan:kompilasi-cl-templat (nama jalur gabungan #P"index.tmpl"*direktori-templat*)) (menghilangkan "/" () (aplikasi saya.tampilan: indeks-render))
cl-penutupan-templat
Dokumentasi Templat Penutupan
Clack - Lingkungan aplikasi web.
Kekurangan - Inti dari Clack.
ningle - Kerangka aplikasi web super mikro yang menjadi dasar Caveman.
Djula - Mesin Templat HTML.
CL-DBI - Pustaka antarmuka yang tidak bergantung pada basis data.
SxQL - perpustakaan pembuat SQL.
Iri - Pengalih konfigurasi.
Roswell - Manajer implementasi Common Lisp.
Eitaro Fukamachi ([email protected])
Berlisensi di bawah Lisensi LLGPL.