;;;; cl-scopes/web/response - set up and render server response (defpackage :scopes/web/response (:use :common-lisp) (:local-nicknames (:core :scopes/core) (:dom :scopes/web/dom) (:message :scopes/core/message) (:shape :scopes/shape)) (:export #:interaction #:setup #:html-response #:render #:render-content #:render-not-found)) (in-package :scopes/web/response) (defvar *html-response-class* nil) (defclass response () ((context :reader context :initarg :context) (env :reader env :initarg :env) (ctype :reader ctype :allocation :class))) (defgeneric render-content (resp msg)) (defclass plain-text-response (response) ((ctype :initform "text/plain"))) (defclass json-response (response) ((ctype :initform "application/json"))) ;;;; server interaction - receive response message from action processing chain (defclass interaction () ((messages :accessor messages :initform nil))) (defmethod print-object ((ia interaction) s) (format s "" (messages ia))) (defmethod core:send ((ia interaction) msg) (log:debug "receiving ~s" msg) (push msg (messages ia))) ;;;; html response (defclass html-response (response) ((ctype :initform "text/html"))) (defmethod render-content ((resp html-response) msg) (dom:render (dom:dlist nil (shape:data msg)))) ;;;; common definitions (defun setup (ctx env &key html-responder) (let* ((headers (getf env :headers)) (resp-class (select-response-class (gethash "accept" headers) html-responder))) (make-instance resp-class :context ctx :env env))) (defun select-response-class (accept html-responder) (let ((accept (string-downcase accept))) (cond ((str:containsp "html" accept) (html-response-class html-responder)) ((str:containsp "json" accept) 'json-response) ((str:containsp "plain" accept) 'plain-text-response) (t (html-response-class html-responder))))) (defun html-response-class (html-responder) (or html-responder *html-response-class* 'html-response)) (defun render (resp iact) ; pre-process special message heads, e.g. (:system :error ...) ; => set status code, provide additional data elements ; set additional headers (let ((headers (list :content-type (ctype resp))) (rcode 200)) #'(lambda (responder) (let ((writer (funcall responder (list rcode headers)))) (dolist (msg (messages iact)) (funcall writer (render-content resp msg))) (funcall writer nil :close t))))) ; (content (mapcar #'(lambda (m) (render-content resp m)) (messages iact)))) ;(list rcode headers content))) (defun render-not-found(resp) (list 404 '(:content-type "text/plain") '("Not found")))