(asdf:load-system "lazybones-hunchentoot") (defpackage #:lazybones-test (:use #:cl) (:local-nicknames (#:lzb #:lazybones)) (:import-from #:lazybones #:defendpoint* #:http-ok #:http-err)) (in-package :lazybones-test) ;; first make a server and add some custom error responses (defvar *server* (lzb:create-server :port 8888)) (defun custom-404 () (format nil "~a wasn't found :(" (lzb:request-path))) ; can use request functiosn (defun custom-403 () "You, in particular, can't do that. :P ") (lzb:set-canned-response *server* 404 'custom-404 "text/plain" ) (lzb:set-canned-response *server* 403 'custom-403 "text/plain" ) (lzb:set-canned-response *server* 500 #p"/path/to/500error.txt" "text/plain" ) ;; 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 (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"))) ;; now we install the app to the server (lzb:install-app *server* (lzb:app)) ; (app) is the default app for this package ;; 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'." (print (lzb:request-body)) ; dummy implementation, prints post body to stdout (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)))