• Stars
    star
    769
  • Rank 57,005 (Top 2 %)
  • Language
    Common Lisp
  • Created about 13 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Lightweight web application framework for Common Lisp.

Caveman2 - Lightweight web application framework

Build Status

Usage

(defparameter *web* (make-instance '<app>))

@route GET "/"
(defun index ()
  (render #P"index.tmpl"))

@route GET "/hello"
(defun say-hello (&key (|name| "Guest"))
  (format nil "Hello, ~A" |name|))

About Caveman2

What's different from Caveman "1"?

Everything. Caveman2 was written from scratch.

These are noteworthy points.

  • Is based on ningle
  • Has database integration
  • Uses new, separate configuration system (Envy)
  • Has new routing macro

The reason I wrote it from scratch:

One of the most frequently asked questions was "Which should I use: ningle or Caveman? What are the differences?" I think these were asked so frequently because Caveman and ningle were too similar. Both of them are called "micro", and had no database support.

With Caveman2, Caveman is no longer a "micro" web application framework. It supports CL-DBI, and has database connection management by default. Caveman has started growing up.

Design Goal

Caveman is intended to be a collection of common parts of web applications. With Caveman2, I use three rules to make decisions:

  • Be extensible.
  • Be practical.
  • Don't force anything.

Quickstart

You came here because you're interested in living like a caveman, right? This isn't Disneyland, but we can start here. Let's get into a cave!

Installation

Caveman2 is now available on Quicklisp.

(ql:quickload :caveman2)

Generating a project skeleton

(caveman2:make-project #P"/path/to/myapp/"
                       :author "<Your full name>")
;-> writing /path/to/myapp/.gitignore
;   writing /path/to/myapp/README.markdown
;   writing /path/to/myapp/app.lisp
;   writing /path/to/myapp/db/schema.sql
;   writing /path/to/myapp/shlyfile.lisp
;   writing /path/to/myapp/myapp-test.asd
;   writing /path/to/myapp/myapp.asd
;   writing /path/to/myapp/src/config.lisp
;   writing /path/to/myapp/src/db.lisp
;   writing /path/to/myapp/src/main.lisp
;   writing /path/to/myapp/src/view.lisp
;   writing /path/to/myapp/src/web.lisp
;   writing /path/to/myapp/static/css/main.css
;   writing /path/to/myapp/t/myapp.lisp
;   writing /path/to/myapp/templates/_errors/404.html
;   writing /path/to/myapp/templates/index.tmpl
;   writing /path/to/myapp/templates/layout/default.tmpl

Start a server

This is an example that assumes that the name of your application is "myapp". Before starting the server, you must first load your app.

(ql:quickload :myapp)

Your application has functions named start and stop to start/stop your web application.

(myapp:start :port 8080)

As Caveman is based on Clack/Lack, you can choose which server to run on -- Hunchentoot, Woo or Wookie, etc.

(myapp:start :server :hunchentoot :port 8080)
(myapp:start :server :fcgi :port 8080)

I recommend you use Hunchentoot on a local machine, and use Woo in a production environment.

You can also start your application by using clackup command.

$ ros install clack
$ which clackup
/Users/nitro_idiot/.roswell/bin/clackup

$ APP_ENV=development clackup --server :fcgi --port 8080 app.lisp

Routing

Caveman2 provides 2 ways to define a route -- @route and defroute. You can use either.

@route is an annotation macro, defined by using cl-annot. It takes a method, a URL-string, and a function.

@route GET "/"
(defun index ()
  ...)

;; A route with no name.
@route GET "/welcome"
(lambda (&key (|name| "Guest"))
  (format nil "Welcome, ~A" |name|))

This is similar to Caveman1's @url except for its argument list. You don't have to specify an argument when it is not required.

defroute is just a macro. It provides the same functionality as @route.

(defroute index "/" ()
  ...)

;; A route with no name.
(defroute "/welcome" (&key (|name| "Guest"))
  (format nil "Welcome, ~A" |name|))

Since Caveman bases on ningle, Caveman also has the Sinatra-like routing system.

;; GET request (default)
@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) () ...)

;; OPTIONS request
@route OPTIONS "/" (lambda () ...)
(defroute ("/" :method :OPTIONS) () ...)

;; For all methods
@route ANY "/" (lambda () ...)
(defroute ("/" :method :ANY) () ...)

Route patterns may contain "keywords" to put the value into the argument.

(defroute "/hello/:name" (&key name)
  (format nil "Hello, ~A" name))

The above controller will be invoked when you access "/hello/Eitaro" or "/hello/Tomohiro", and name will be "Eitaro" or "Tomohiro", as appropriate.

(&key name) is almost same as a lambda list of Common Lisp, except it always allows other keys.

(defroute "/hello/:name" (&rest params &key name)
  ;; ...
  )

Route patterns may also contain "wildcard" parameters. They are accessible by using splat.

(defroute "/say/*/to/*" (&key splat)
  ; matches /say/hello/to/world
  (format nil "~A" splat))
;=> (hello world)

(defroute "/download/*.*" (&key splat)
  ; matches /download/path/to/file.xml
  (format nil "~A" splat)) 
;=> (path/to/file xml)

If you'd like to write use a regular expression in a URL rule, :regexp t should work.

(defroute ("/hello/([\\w]+)" :regexp t) (&key captures)
  (format nil "Hello, ~A!" (first captures)))

Normally, routes are tested for a match in the order they are defined, and only the first route matched is invoked, with the following routes being ignored. However, a route can continue testing for matches in the list, by including next-route.

(defroute "/guess/:who" (&key who)
  (if (string= who "Eitaro")
      "You got me!"
      (next-route)))

(defroute "/guess/*" ()
  "You missed!")

You can return following formats as the result of defroute.

  • String
  • Pathname
  • Clack's response list (containing Status, Headers and Body)

Redirection

Redirect to another route with(redirect "url"). A second optional argument is the status code, 302 by default.

Reverse URLs

When you defined routes with names, you can find the URL from a name with (url-for route-name &rest params).

The function will throw an error if no route is found.

More helper functions

See also:

  • add-query-parameters base-url params

Structured query/post parameters

Parameter keys containing square brackets ("[" & "]") will be parsed as structured parameters. You can access the parsed parameters as _parsed in routers.

<form action="/edit">
  <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\" . \"[email protected]\") (\"birth\" . ((\"year\" . 2000) (\"month\" . 1) (\"day\" . 1))))"

;; With assoc-utils
(ql:quickload :assoc-utils)
(import 'assoc-utils:aget)
(defroute "/edit" (&key _parsed)
  (format nil "~S" (aget _parsed "person")))

Blank keys mean they have multiple values.

<form action="/add">
  <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=)))
;=> "(((\"name\" . \"WiiU\") (\"price\" . \"30000\")) ((\"name\" . \"PS4\") (\"price\" . \"69000\")))"

Templates

Caveman uses Djula as its default templating engine.

{% extends "layouts/default.html" %}
{% block title %}Users | MyApp{% endblock %}
{% block content %}
<div id="main">
  <ul>
  {% for user in users %}
    <li><a href="{{ user.url }}">{{ user.name }}</a></li>
  {% endfor %}
  </ul>
</div>
{% endblock %}
(import 'myapp.view:render)

(render #P"users.html"
        '(:users ((:url "/id/1"
                   :name "nitro_idiot")
                  (:url "/id/2"
                   :name "meymao"))
          :has-next-page T))

If you want to get something from a database or execute a function using Djula you must explicity call list when passing the arguments to render so that the code executes.

(import 'myapp.view:render)

(render #P"users.html"
        (list :users (get-users-from-db)))

JSON API

This is an example of a 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 is a part of a skeleton project. You can find its code in "src/view.lisp".

Static file

Images, CSS, JS, favicon.ico and robot.txt in "static/" directory will be served by 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

You can change these rules by rewriting "PROJECT_ROOT/app.lisp". See Clack.Middleware.Static for detail.

Configuration

Caveman adopts Envy as a configuration switcher. This allows definition of multiple configurations and switching between them according to an environment variable.

This is a typical example:

(defpackage :myapp.config
  (:use :cl
        :envy))
(in-package :myapp.config)

(setf (config-env-var) "APP_ENV")

(defconfig :common
  `(:application-root ,(asdf:component-pathname (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" :username "whoami" :password "1234")
     (:workerdb :mysql :database-name "jobs" :username "whoami" :password "1234"))))

(defconfig |staging|
  `(:debug T
    ,@|production|))

Every configuration is a property list. You can choose the configuration which to use by setting APP_ENV.

To get a value from the current configuration, call myapp.config:config with the key you want.

(import 'myapp.config:config)

(setf (osicat:environment-variable "APP_ENV") "development")
(config :debug)
;=> T

Database

When you add :databases to the configuration, Caveman enables database support. :databases is an association list of database settings.

(defconfig |production|
  '(:databases
    ((:maindb :mysql :database-name "myapp" :username "whoami" :password "1234")
     (:workerdb :mysql :database-name "jobs" :username "whoami" :password "1234"))))

db in a package myapp.db is a function for connecting to each databases configured the above. Here is an example.

(use-package '(:myapp.db :sxql :datafly))

(defun search-adults ()
  (with-connection (db)
    (retrieve-all
      (select :*
        (from :person)
        (where (:>= :age 20))))))

The connection is alive during the Lisp session, and will be reused in every HTTP request.

retrieve-all and the query language came from datafly and SxQL. See those sets of documentation for more information.

Set HTTP headers or HTTP status

There are several special variables available during a HTTP request. *request* and *response* represent a request and a response. If you are familiar with Clack, these are instances of subclasses of Clack.Request and Clack.Response.

(use-package :caveman2)

;; Get a value of Referer header.
(http-referer *request*)

;; Set Content-Type header.
(setf (getf (response-headers *response*) :content-type) "application/json")

;; Set HTTP status.
(setf (status *response*) 304)

If you would like to set Content-Type "application/json" for all "*.json" requests, next-route can be used.

(defroute "/*.json" ()
  (setf (getf (response-headers *response*) :content-type) "application/json")
  (next-route))

(defroute "/user.json" () ...)
(defroute "/search.json" () ...)
(defroute ("/new.json" :method :POST) () ...)

Using session

Session data is for memorizing user-specific data. *session* is a hash table that stores session data.

This example increments :counter in the session, and displays it for each visitor.

(defroute "/counter" ()
  (format nil "You came here ~A times."
          (incf (gethash :counter *session* 0))))

Caveman2 stores session data in-memory by default. To change this, specify :store to :session in "PROJECT_ROOT/app.lisp".

This example uses RDBMS to store session data.

      '(:backtrace
        :output (getf (config) :error-log))
      nil)
- :session
+ (:session
+  :store (make-dbi-store :connector (lambda ()
+                                      (apply #'dbi:connect
+                                             (myapp.db:connection-settings)))))
  (if (productionp)
      nil
      (lambda (app)

NOTE: Don't forget to add :lack-session-store-dbi as :depends-on of your app. It is not a part of Clack/Lack.

See the source code of Lack.Session.Store.DBi for more information.

Throw an HTTP status code

(import 'caveman2:throw-code)

(defroute ("/auth" :method :POST) (&key |name| |password|)
  (unless (authorize |name| |password|)
    (throw-code 403)))

Specify error pages

To specify error pages for 404, 500 or such, define a method on-exception of your app.

(defmethod on-exception ((app <web>) (code (eql 404)))
  (declare (ignore app code))
  (merge-pathnames #P"_errors/404.html"
                   *template-directory*))

Hot Deployment

Though Caveman doesn't have a feature for hot deployment, Server::Starter -- a Perl module -- makes it easy.

$ APP_ENV=production start_server --port 8080 -- clackup --server :fcgi app.lisp

NOTE: Server::Starter requires the server to support binding on a specific fd, which means only :fcgi and :woo are the ones work with start_server command.

To restart the server, send HUP signal (kill -HUP <pid>) to the start_server process.

Error Log

Caveman outputs error backtraces to a file which is specified at :error-log in your configuration.

(defconfig |default|
  `(:error-log #P"/var/log/apps/myapp_error.log"
    :databases
    ((:maindb :sqlite3 :database-name ,(merge-pathnames #P"myapp.db"
                                                        *application-root*)))))

Use another templating library

CL-WHO

(import 'cl-who:with-html-output-to-string)

(defroute "/" ()
  (with-html-output-to-string (output nil :prologue t)
    (:html
      (:head (:title "Welcome to Caveman!"))
      (: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>Welcome to Caveman!</title></head><body>Blah blah blah.</body></html>"

CL-Markup

(import 'cl-markup:xhtml)

(defroute "/" ()
  (xhtml
    (:head (:title "Welcome to Caveman!"))
    (: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>Welcome to Caveman!</title></head><body>Blah blah blah.</body></html>"

cl-closure-template

{namespace myapp.view}

{template renderIndex}
<!DOCTYPE html>
<html>
<head>
  <title>"Welcome to Caveman!</title>
</head>
<body>
  Blah blah blah.
</body>
</html>
{/template}
(import 'myapp.config:*template-directory*)

(closure-template:compile-cl-templates (merge-pathnames #P"index.tmpl"
                                                        *template-directory*))

(defroute "/" ()
  (myapp.view:render-index))

See Also

  • Clack - Web application environment.
  • Lack - The core of Clack.
  • ningle - Super micro web application framework that Caveman is based on.
  • Djula - HTML Templating engine.
  • CL-DBI - Database-independent interface library.
  • SxQL - SQL builder library.
  • Envy - Configuration switcher.
  • Roswell - Common Lisp implementation manager.

Author

License

Licensed under the LLGPL License.

More Repositories

1

woo

A fast non-blocking HTTP server on top of libev
Common Lisp
1,255
star
2

clack

Web server abstraction layer for Common Lisp
Common Lisp
1,021
star
3

qlot

A project-local library installer for Common Lisp
Common Lisp
384
star
4

dexador

A fast HTTP client for Common Lisp
Common Lisp
358
star
5

sxql

An SQL generator for Common Lisp.
Common Lisp
349
star
6

fast-http

A fast HTTP request/response parser for Common Lisp.
Common Lisp
338
star
7

mito

An ORM for Common Lisp with migrations, relationships and PostgreSQL support
Common Lisp
270
star
8

ningle

Super micro framework for Common Lisp
Common Lisp
254
star
9

cl-project

Generate modern project skeletons
Common Lisp
234
star
10

prove

Yet another unit testing framework for Common Lisp
Common Lisp
217
star
11

cl-dbi

Database independent interface for Common Lisp
Common Lisp
195
star
12

rove

#1=(yet another . #1#) common lisp testing library
Common Lisp
139
star
13

lack

Lack, the core of Clack
Common Lisp
137
star
14

quri

Yet another URI library for Common Lisp
Common Lisp
105
star
15

websocket-driver

WebSocket server/client implementation for Common Lisp
Common Lisp
101
star
16

datafly

A lightweight database library for Common Lisp.
Common Lisp
97
star
17

utopian

A web framework for Common Lisp never finished.
Common Lisp
95
star
18

lsx

Embeddable HTML templating engine for Common Lisp with JSX-like syntax
Common Lisp
78
star
19

shelly

[OBSOLETE] Use Roswell instead.
Common Lisp
63
star
20

envy

Configuration switcher by an environment variable inspired by Config::ENV.
Common Lisp
57
star
21

integral

[OBSOLETE] Use Mito instead.
Common Lisp
54
star
22

mondo

Simple Common Lisp REPL
Common Lisp
52
star
23

psychiq

Background job processing for Common Lisp
Common Lisp
51
star
24

getac

Quick unit testing tool for competitive programming
Common Lisp
46
star
25

dockerfiles

Dockerfiles for Common Lisp programming
Shell
40
star
26

proc-parse

Procedural vector parser
Common Lisp
36
star
27

jose

A JOSE implementation
Common Lisp
32
star
28

redmine-el

See Redmine on Emacs
Emacs Lisp
30
star
29

legion

Simple multithreading worker mechanism.
Common Lisp
30
star
30

cl-coveralls

Common Lisp
29
star
31

docker-cl-example

Example projects to run/develop Common Lisp web application on Docker container
Common Lisp
28
star
32

L5

Yet Another Presentation Tool for Lispers
Clojure
28
star
33

event-emitter

Event mechanism for Common Lisp objects.
Common Lisp
28
star
34

.lem

Lem configuration files
Common Lisp
27
star
35

assoc-utils

Utilities for manipulating association lists.
Common Lisp
25
star
36

clozure-cl

Unofficial mirror of Clozure CL
Common Lisp
25
star
37

supertrace

Superior Common Lisp `trace` functionality for debugging/profiling real world applications.
Common Lisp
25
star
38

myway

Sinatra-compatible URL routing library for Common Lisp
Common Lisp
24
star
39

fast-websocket

Optimized low-level WebSocket protocol parser written in Common Lisp
Common Lisp
22
star
40

uncl

Un-Common Lisp on Common Lisp
Common Lisp
22
star
41

cl-locale

Simple i18n library for Common Lisp
Common Lisp
21
star
42

ragno

Common Lisp Web crawling library based on Psychiq.
Common Lisp
19
star
43

safety-params

Check params
Common Lisp
19
star
44

anypool

General-purpose connection pooling library for Common Lisp
Common Lisp
19
star
45

mito-auth

User authorization for Mito classes.
Common Lisp
18
star
46

cl-cookie

HTTP cookie manager
Common Lisp
18
star
47

xsubseq

Efficient way to use "subseq"s in Common Lisp
Common Lisp
16
star
48

smart-buffer

Smart octets buffer.
Common Lisp
16
star
49

http-body

HTTP POST data parser.
Common Lisp
15
star
50

re21

CL21's spin-off project that provides neat APIs for regular expressions.
Common Lisp
15
star
51

webapi

CLOS-based wrapper builder for Web APIs.
Common Lisp
15
star
52

lev

libev bindings for Common Lisp
Common Lisp
15
star
53

.emacs.d

My .emacs.d
Emacs Lisp
15
star
54

lesque

[OBSOLETE] Use Psychiq instead.
Common Lisp
14
star
55

pem

PEM parser.
Common Lisp
14
star
56

circular-streams

Circularly readable streams for Common Lisp.
Common Lisp
14
star
57

mito-attachment

Mito mixin class for file management outside of RDBMS
Common Lisp
14
star
58

emacs-config

[OBSOLETE] More simplified version is
Emacs Lisp
13
star
59

yapool

A Common Lisp command-line tool for executing shell commands via SSH.
12
star
60

cl-line-bot-sdk

SDK for the LINE Messaging API for Common Lisp
Common Lisp
11
star
61

id3v2

ID3v2 parser
Common Lisp
10
star
62

kindly-mode

Amazon Kindle-like view mode for Emacs.
Emacs Lisp
10
star
63

wsock

Low-level UNIX socket library
Common Lisp
9
star
64

asn1

ASN.1 decoder
Common Lisp
9
star
65

can

A role-based access right control library.
Common Lisp
8
star
66

hatenablog-theme-writer

็‰ฉๆ›ธใใฎใŸใ‚ใฎใƒ–ใƒญใ‚ฐใƒ†ใƒผใƒžใ€ŒWriterใ€ for ใฏใฆใชใƒ–ใƒญใ‚ฐ
CSS
7
star
67

neovim-config

~/.config/nvim
Vim Script
7
star
68

swank-js

Swank backend for Node.JS and in-browser JavaScript
JavaScript
7
star
69

clee

Common Lisp Event Engine
Common Lisp
6
star
70

fukacl

Fukamachi Common Lisp Package
Common Lisp
6
star
71

ponzu.db

O/R Mapper, a part of Ponzu Framework, for Common Lisp
Common Lisp
6
star
72

gotanda

Common Lisp
6
star
73

clbuild

Unofficial fork of clbuild
Shell
5
star
74

mp3-duration

Get the duration of an MP3 file
Common Lisp
5
star
75

asdf-c-test-file

Provides ASDF component :test-file.
Common Lisp
5
star
76

sxql-abstract

An abstraction layer for SQL between RDBMS.
Common Lisp
5
star
77

as-interval

An extension of cl-async for introducing 'interval' feature.
Common Lisp
5
star
78

fukamachi.github.com

HTML
4
star
79

p5-shelly

[DEPRECATED] Moved to https://github.com/fukamachi/shelly
Perl
4
star
80

github-webhook

Docker container to listen for GitHub webhook events
Common Lisp
4
star
81

dont-type-twice-el

Supports your effective text editing.
Emacs Lisp
4
star
82

nail

Common Lisp
4
star
83

lem-vi-sexp

vim-sexp port for Lem
Common Lisp
4
star
84

multival-plist

Property List stores multiple values per one key.
Common Lisp
4
star
85

kunitada-bot

All tweets should be "fastest".
Ruby
3
star
86

trivial-utf-8

Imported from the original darcs repo.
Common Lisp
3
star
87

closure-library-skeleton

Skeleton files for a project using Google Closure Library.
JavaScript
3
star
88

clack-doc

[DEPRECATED] Documentation tool for Clack (I moved them to Quickdocs.org)
Common Lisp
3
star
89

ac-swift

Swift auto completion for Emacs
Emacs Lisp
3
star
90

gotumda

Put all your tasks into one bucket.
JavaScript
3
star
91

opImKayacComPlugin

PHP
2
star
92

partial-bench

A tiny benchmarking library to get a running time of a specific part.
Common Lisp
2
star
93

p5-gotumda

Communicate over tasks.
JavaScript
2
star
94

swank.ros

Common Lisp
2
star
95

Plack-Middleware-Try

Plack Middleware to catch exceptions.
Perl
2
star
96

sourcekit

Textmate Like Editor Inside Chrome
JavaScript
2
star
97

Plack-Middleware-StackTraceLog

Plack Middleware for logging when your app dies.
Perl
1
star
98

cl-weather-jp

Get weather in Japan
Common Lisp
1
star
99

rove-test-example

Common Lisp
1
star