"ningle" is a lightweight web application framework for Common Lisp.
(defvar *app* (make-instance 'ningle:app))
(setf (ningle:route *app* "/")
"Welcome to ningle!")
(setf (ningle:route *app* "/login" :method :POST)
#'(lambda (params)
(if (authorize (cdr (assoc "username" params :test #'string=))
(cdr (assoc "password" params :test #'string=)))
"Authorized!"
"Failed...Try again.")))
(clack:clackup *app*)
Now you can access to http://localhost:5000/ and then ningle should show you "Welcome to ningle!".
(ql:quickload :ningle)
ningle is a fork project of Caveman. ningle doesn't require you to generate a project skeleton.
As this is a thin framework, you need to have subtle knowledge about Clack. It is a server interface ningle bases on.
ningle has the Sinatra-like routing system.
;; GET request (default)
(setf (ningle:route *app* "/" :method :GET) ...)
;; POST request
(setf (ningle:route *app* "/" :method :POST) ...)
;; PUT request
(setf (ningle:route *app* "/" :method :PUT) ...)
;; DELETE request
(setf (ningle:route *app* "/" :method :DELETE) ...)
;; OPTIONS request
(setf (ningle:route *app* "/" :method :OPTIONS) ...)
Route pattern may contain "keyword" to put the value into the argument.
(setf (ningle:route *app* "/hello/:name")
#'(lambda (params)
(format nil "Hello, ~A" (cdr (assoc :name params)))))
The above controller will be invoked when you access to "/hello/Eitaro" or "/hello/Tomohiro", and then (cdr (assoc :name params))
will be "Eitaro" and "Tomohiro".
Route patterns may also contain "wildcard" parameters. They are accessible by (assoc :splat params)
.
(setf (ningle:route *app* "/say/*/to/*")
#'(lambda (params)
; matches /say/hello/to/world
(cdr (assoc :splat params)) ;=> ("hello" "world")
))
(setf (ningle:route *app* "/download/*.*")
#'(lambda (params)
; matches /download/path/to/file.xml
(cdr (assoc :splat params)) ;=> ("path/to/file" "xml")
))
Route matching with Regular Expressions:
(setf (ningle:route *app* "/hello/([\\w]+)" :regexp t)
#'(lambda (params)
(format nil "Hello, ~A!" (first (cdr (assoc :captures params))))))
Routes may include a variety of matching conditions, such as the Accept:
(setf (ningle:route *app* "/" :accept '("text/html" "text/xml"))
#'(lambda (params)
(declare (ignore params))
"<html><body>Hello, World!</body></html>"))
(setf (ningle:route *app* "/" :accept "text/plain")
#'(lambda (params)
(declare (ignore params))
"Hello, World!"))
You can easily define your own conditions:
(setf (ningle:requirement *app* :probability)
#'(lambda (value)
(<= (random 100) value)))
(setf (ningle:route *app* "/win_a_car" :probability 10)
#'(lambda (params)
(declare (ignore params))
"You won!"))
(setf (ningle:route *app* "/win_a_car")
#'(lambda (params)
(declare (ignore params))
"Sorry, you lost."))
ningle provides two special variables named *request*
and *response*
. They will be bound to an instance Lack.Request and Lack.Response for each request.
For example, by using them, you can change the response status code, Content-Type or something like that in each controllers.
(setf (lack.response:response-headers *response*)
(append (lack.response:response-headers *response*)
(list :content-type "application/json")))
(setf (lack.response:response-headers *response*)
(append (lack.response:response-headers *response*)
(list :access-control-allow-origin "*")))
(setf (lack.response:response-status *response*) 201)
ningle provides a useful function named context
. It is an accessor to an internal hash table.
(setf (context :database)
(dbi:connect :mysql
:database-name "test-db"
:username "nobody"
:password "nobody"))
(context :database)
;;=> #<DBD.MYSQL:<DBD-MYSQL-CONNECTION> #x3020013D1C6D>
ningle doesn't provide Session system in the core, but recommends to use Lack.Middleware.Session with Lack.Builder.
(import 'lack.builder:builder)
(clack:clackup
(builder
:session
*app*))
Of course, you can use other Lack Middlewares with ningle.
- Eitaro Fukamachi (e.arrows@gmail.com)
Copyright (c) 2012-2014 Eitaro Fukamachi (e.arrows@gmail.com)
Licensed under the LLGPL License.