aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Okay <okay@toyful.space>2022-02-08 12:33:42 -0600
committerColin Okay <okay@toyful.space>2022-02-08 12:33:42 -0600
commit6bca2e642b64ff8d4c66e425e4ea266641e86c5d (patch)
tree483c0e022467aafed3b7f745524a44e20202edd4
parentca67709947d065784b219f184a17ee20fcb38b3b (diff)
updated readme
-rw-r--r--README.md219
1 files changed, 138 insertions, 81 deletions
diff --git a/README.md b/README.md
index 799bdcc..1c3d386 100644
--- a/README.md
+++ b/README.md
@@ -1,101 +1,158 @@
# What
-`lazybones` is a half-hearted effort at a small HTTP route handling
-framework for smallish dynamic sites, preferably those whose state
-lives in the lisp process.
+`lazybones` is a small HTTP routing framework.
-It is not at all 'field tested', does not compete with more thorougly
-conceived frameworks, and is itself just a kind of wrapper around
-clack.
+Features include:
-I use lazybones for personal projects. Here are a few:
+- Different server backends. (At the moment only Hunchnetoot is supported)
+- Modular and "installable" applications.
+- Handy macros for provisioning apps and defining endpoints.
+- Livecoding for your endpoint handlers and application confiturations.
+- Automatic documentation generation for `lazybones:app` instances.
-- [Time Well Spent](https://github.com/cbeo/tws) is a time tracker with [EBS](https://www.joelonsoftware.com/2007/10/26/evidence-based-scheduling/) features.
-- [Compost](https://github.com/cbeo/compost) a message board in a single deployable binary, I use it for small group collaborations.
+Although lazybones can be used to develop and serve page-oriented web
+sites, it has been written to help me develop "self documenting" HTTP
+APIs.
-# Basic Example
-``` lisp
-(defpackage #:hello-site
- (:use #:cl)
- (import-from #:lazybones
- #:defroute
- #:http-ok
- #:http-err)
- (import-from #:fictitious-image-db-package
- #:get-image-ids
- #:lookup-image
- #:image-mimetype
- #:image-data))
-
-(in-package :hello-site)
-
-(defroute :get "/hello"
- (http-ok "text/html"
- (spinneret:with-html-string
- (:doctype)
- (:html :lang "en"
- (:head
- (:meta :charset "utf-8")
- (:title "some images"))
- (:body
- (:h1 "Hello")
- (:ul
- (dolist (img-id (get-image-ids)) ; assuming some function to get ids
- (:li (:img :src (format nil "/image/~a" img-id))))))))))
-
-(defroute :get "/image/:id"
- (alexandria:if-let (found (lookup-image id)) ; assuming a funciton to lookup images
- (http-ok (image-mimetype found) ; and to query properties
- (image-data found)) ; a byte vector
- (http-err 404 "Not Found")))
-
-
-(start :port 5000) ; start the server
-
-```
-
-Example code that would serve a page containing a list of images.
+## Main components
-## Helpful Features
+The two main components are the classes `lazybones:app` and
+`lazybones:endpoint`. Endpoints are objects representing everything
+needed to handle an HTTP request. Endpoints are collected into groups
+called apps. Apps, in addition to being collections of endpoints, are
+the unit of development for larger lazybones projects. Apps can be
+"installed to" and "uninstalled from" a listening server on the fly.
-Inside of any handler there is a dynamically bound variable `*req*`
-that holds the HTTP request being processed.
-Using a form called `with-handler-preamble`, groups of handlers can be
-defined that all perform some initialization / access control steps.
+## Example
-For example:
+The following is quick example showing a few things that `lazybones` can do.
``` lisp
+(asdf:load-system "lazybones-hunchentoot")
-(with-handler-preamble
- ((unless (authorized-request *req*)
- (http-err 401 "Unauthorized"))
-
- (make-database-connection))
-
- (defroute :post "/image/:id/edit"
- ;; ... handle image post ...
- )
-
- (defroute :delete "/image/:id"
- ;; ... handle image delete ...
- ))
+(defpackage #:lazybones-test
+ (:use #:cl)
+ (:local-nicknames (#:lzb #:lazybones))
+ (:import-from #:lazybones
+ #:defendpoint*
+ #:http-ok
+ #:http-err))
+
+(in-package :lazybones-test)
+
+;; PPROVISION-APP makes an app. You can supply an optional name, a symbol.
+;; In lieu of a supplied name, the name of the package is used as the app's name.
+
+(lzb:provision-app ()
+ :title "Lazybones Demo App"
+ :version "0.0.0"
+ :description "Just an API that defines some endpoints. These
+ endpoints aren't meant to accomplish anything. merely to test out
+ the lazybones HTTP routing framework."
+
+ :content-type "text/plain" ; default content type of server responses.
+ :auth 'post-authorizer ; default authorizor for requests that need it
+ 404 'custom-404 ; custom content for error response codes.
+ 403 'custom-403)
+
+(defun custom-404 ()
+ (format nil "~a wasn't found :(" (lzb:request-path)))
+
+(defun custom-403 ()
+ "You, in particular, can't do that. :P ")
+
+(defun post-authorizer ()
+ "Request is authorized if it contains the right TESTAPPSESSION
+ cookie. Obtain such a cookie by posting to the /login endpoint."
+ (string-equal "coolsessionbro" (lzb:request-cookie "testappsession")))
+
+;; DEFENDPOINT* is a macro to define an endpoint and install it into the
+;; app whose name is the current package anme. DEFENDPOINT (without the *)
+;; allows you to explictly specify the app where the endpoint is installed.
+
+(defendpoint* :post "/login" ()
+ "Dummy login endpoint for returning a session cookie. Always returns
+ the \"true\" and sends a set-cookie header, setting 'testappsession'
+ to 'coolsessionbro'."
+ (setf (lzb:response-cookie "testappsession") "coolsessionbro")
+ (http-ok "true"))
+
+(defendpoint* :get "/hello/:who:" ()
+ "Just says hello to WHO"
+ (http-ok (format nil "Hello ~a" who)))
+
+(defendpoint* :post "/hello/:who:" (:auth t) ; use the default authorizor for the app
+ "Post something to hello who"
+ (print (lzb:request-header :content-type))
+ (let ((body (lzb:request-body)))
+ (http-ok (format nil "Hello ~a, I got your message ~a"
+ who body))))
+
+(defendpoint* :get "/search" ()
+ "Echo the search parameters in a nice list."
+ (http-ok (format nil "Query Was:~%~{~a is ~a~%~}~%"
+ (loop for (x . y) in (lzb:request-parameters)
+ collect x
+ collect y))))
+
+(defun crapshoot-authorizer ()
+ "Randomly decides that the request is authorized"
+ (< 5 (random 10)))
+
+(defendpoint* :post "/search" (:auth 'crapshoot-authorizer) ; use custom authorizer
+ "Echo the search parameters in a nice list, but also has a post-body"
+ (http-ok
+ (with-output-to-string (out)
+ (format out "Query Was:~%~{~a is ~a~%~}~%"
+ (loop for (x . y) in (lzb:request-parameters)
+ collect x
+ collect y))
+ (terpri)
+ (format out "Decoded Post Body: ~s~%" (lzb:request-body)))))
+
+
+(defun to-int (string)
+ "An Integer"
+ (parse-integer string))
+
+;; route variables can accept parsers / preformatters
+;; these will parse a value and supply it to the argument of the handler.
+;; int eh following CATEGORY is an int
+
+(defendpoint* :get "/search/:category to-int:" ()
+ "Echo the search back, but in a specific category"
+ (assert (typep category 'integer)) ; just to show you.
+ (http-ok
+ (format nil "Searching in ~a with parameters:~%~{~a = ~a~%~}~%"
+ category
+ (loop for (x . y) in (lzb:request-parameters)
+ collect x
+ collect y))))
+
+(defun person-by-id (id)
+ "A Person Instance"
+ ;; The real thing might perform some database operation here. If the
+ ;; operation failed, an error could be signalled, in which case a
+ ;; 500 response would be sent to the client.
+ (list :name "Colin" :occupation "Macrologist" :id (parse-integer id)))
+
+(defendpoint* :get "/person/:person person-by-id:" (:content-type "application/json")
+ "Returns a json representation of the person."
+ (http-ok
+ (jonathan:to-json person)))
```
-# Installation
-
-If you insist on trying `lazybones` for yourself, you'll need to
-ensure that your quicklisp can find it.
-
-The easiest approach is probably something like:
+## Backends
- cd ~/quicklisp/local-projects/
- git clone https://github.com/cbeo/lazybones
-
-With those packages available to quicklisp, you should be able to do
-`(ql:quickload :lazybones)` in your REPL.
+**WARNING** Users can mostly ignore thissection. The API for alternate backends is in
+flux.
+To implement a new backend for lazybones, consult the
+`lazybones.backend` package. Define a new system and a new package
+that `uses` the lazybones.backend package. Implement functions for
+each symbol. Consult the `lazybones-hunchentoot` system for an
+example backend.