Getting started with clack
Clack is a very simple framework for unifying the different lisp web application servers. However there isn't a lot of documentation for it. This page hopes to address this.
Annotated examples of usage
Formatting
All input and output are presented in monospaced preformatted blocks. The color indicates what sort they are:
"Lisp code has a gray background; it can be typed at the REPL"
NIL
: shell code has a green background, it can be typed at a shell prompt
(format t "Output from running commands has a yellow background; do not type this")
Output from running commands has a yellow background; do not type this
Load dependencies used in examples:
(ql:quickload '(clack alexandria optima))
(use-package :optima)
To load "clack": Load 1 ASDF system: clack ; Loading "clack" ... To load "alexandria": Load 1 ASDF system: alexandria ; Loading "alexandria" To load "optima": Load 1 ASDF system: optima ; Loading "optima"
Start a simple server
The only required argument for clackup
is the application; the
simplest form of the application is a function of one argument.
The function should retun a list of the form (http-response-code http-headers-alist &optional body)
body can be a vector of (unsigned-byte 8)
, a pathname, or a list of strings.
(defparameter *clack-server* (clack:clackup (lambda (env)
'(200 nil ("Hello, World!")))))
Hunchentoot server is started. Listening on localhost:5000.
Let's test it with curl:
curl -s http://localhost:5000
Hello, World!
Stop the server
(clack:stop *clack-server*)
Redefining the handler
It's a bit of a pain to have to restart the server all the time, let's make a redefinable handler:
(defun handler (env) '(200 nil ("Hello World, redefinable!")))
And start the server; we call the function by name to allow redefinition
(defparameter *clack-server*
(clack:clackup (lambda (env) (funcall 'handler env))))
Hunchentoot server is started. Listening on localhost:5000.
Check that it works…
curl -s http://localhost:5000
Hello World, redefinable!
Now let's redefine it and take a look at what is in the environment:
(defun handler (env)
`(200 nil (,(prin1-to-string env))))
View results…
curl -s http://localhost:5000
(:REQUEST-METHOD :GET :SCRIPT-NAME "" :PATH-INFO "/" :SERVER-NAME "localhost" :SERVER-PORT 5000 :SERVER-PROTOCOL :HTTP/1.1 :REQUEST-URI "/" :URL-SCHEME "http" :REMOTE-ADDR "127.0.0.1" :REMOTE-PORT 53824 :QUERY-STRING NIL :RAW-BODY #<FLEXI-STREAMS:FLEXI-IO-STREAM {1021B536E3}> :CONTENT-LENGTH NIL :CONTENT-TYPE NIL :CLACK.STREAMING T :CLACK.IO #<CLACK.HANDLER.HUNCHENTOOT::CLIENT {1021B537F3}> :HEADERS #<HASH-TABLE :TEST EQUAL :COUNT 3 {1021B53C13}>)
This is the core part of clack; the environment plist.
Documentation for it is available in the lack README
The fact that it is a plist means capturing values of interest can be done with destructuring-bind
:
(defun handler (env)
(destructuring-bind (&key request-method path-info request-uri
query-string headers &allow-other-keys)
env
`(200
nil
(,(format nil "Method: ~S Path: ~S URI: ~A Query: ~S~%Headers: ~S"
request-method path-info request-uri query-string
(alexandria:hash-table-alist headers))))))
curl -s http://localhost:5000
Method: :GET Path: "/" URI: / Query: NIL Headers: (("accept" . "*/*") ("user-agent" . "curl/7.53.0") ("host" . "localhost:5000"))
Optima can be useful too:
(defun handler (env)
(optima:match env
((guard (property :path-info path)
(alexandria:starts-with-subseq "/foo/" path))
`(200 nil (,(format nil "The path '~A' is in /foo/~%" path))))
((guard (property :path-info path)
(alexandria:starts-with-subseq "/bar/" path))
`(200 nil (,(format nil "The path '~A' is in /bar/~%" path))))
((property :path-info path)
`(404 nil (,(format nil "Path ~A not found~%" path))))))
curl -s http://localhost:5000/foo/quux
curl -s http://localhost:5000/bar/quux
curl -s http://localhost:5000/baz/quux
The path '/foo/quux' is in /foo/ The path '/bar/quux' is in /bar/ Path /baz/quux not found
Public API
CLACK:CLACKUP
Syntax:
clackup app &key server port debug silent use-thread use-default-middlewares &allow-other-keys
=> handler
Arguments and Values:
-
app–A designator for a function of one argument;or a subclass of
lack.component:lack-component
;or a pathname; or a string.
-
server–A symbol. The default is
:hunchentoot
-
port–An integer. The default is 5000
-
debug–A boolean. The default is
t
-
silent–A boolean. The default is
nil
-
use-thread–A boolean. The default is
t
on systems that support threading andnil
otherwise.
-
use-default-middlewares–A boolean. The default is
t
-
handler–A clack.handler::handler.
Description:
clackup
starts a server using the backend designated by server on
port port.
app is used to build the handler chain for the server as follows:
- If app is a function then it will be used directly, and called on each request with the requst environment as its only parameter
- If app is a subclass of
lack.component:lack-component
then (lack.component:call app environment) will be called on every request - If app is a pathname then it will be treated as a lisp file to be evaluated. The result of the last form of the file will be used as above
- If app is a string, then it will be coerced to a pathname and used as above.
- If use-default-middlewares is true then app will be wrapped by the default middlewares
server designates the backend to use; if the backend is not found,
then clackup
will attempt to load it via quicklisp
or asdf
.
port specifies which port to listen on.
debug specifies that debug mode is on. The results of this is backend specific, but typically will handle all errors in the body of app by returning a 500 response to the user if false
silent Suppresses printing of status messages.
use-thread If true, the backend is launched in a separate thread.