fukamachi / ningle

Super micro framework for Common Lisp
http://8arrow.org/ningle/
278 stars 24 forks source link

ningle

Build Status

"ningle" is a lightweight web application framework for Common Lisp.

Usage

(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!".

Installation

(ql:quickload :ningle)

Description

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.

Getting started

Routing

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))))))

Requirements

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."))

Request & Response

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)

Context

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>

Using Session

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.

See Also

Author

Copyright

Copyright (c) 2012-2014 Eitaro Fukamachi (e.arrows@gmail.com)

License

Licensed under the LLGPL License.