Source reorg.

This commit is contained in:
David Botton 2020-12-15 21:46:08 -05:00
parent d7ae4c1bfd
commit f64bc7df44
5 changed files with 307 additions and 226 deletions

220
clog.lisp
View file

@ -26,165 +26,44 @@ application."
(clog asdf:system)
(@clog-top-level section))
(@clog-top-level section))
(defsection @clog-top-level (:title "CLOG Top level")
"CLOG system startup and shutdown"
(*verbose-output* variable)
"CLOG Startup and Shutdown"
(initialize function)
(shutdown function)
(set-on-connect function)
(set-on-connect function)
"CLOG base class"
(attach function)
"CLOG utilities"
(alert-box function)
(escape-string function)
(open-browser function))
(defclass base ()
((connection-id
:accessor connection-id
:initarg :connection-id)
(web-id
:accessor web-id
:initarg :web-id)))
(defun attach (connection-id web-id)
(make-instance 'base :connection-id connection-id :web-id web-id))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Implementation - clog
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar *verbose-output* t "Verbose server output (default true)")
(defvar *app* nil "Clack 'app' middle-ware")
(defvar *client-handler* nil "Clack 'handler' for socket traffic")
(defvar *on-connect-handler* nil "New connection event handler.")
(defvar *new-id* 0 "Connection IDs")
(defvar *connections* (make-hash-table) "Connections to IDs")
(defvar *connection-ids* (make-hash-table) "IDs to connections")
(defvar *connection-lock* (bordeaux-threads:make-lock)
"Protect the connection hash tables")
(defvar *queries-lock* (bordeaux-threads:make-lock)
"Protect query hash tables")
(defvar *queries* (make-hash-table) "Query ID to Answers")
(defvar *queries-sems* (make-hash-table) "Query ID to semiphores")
(defvar *query-time-out* 3 "Number of seconds to timeout waiting for a query")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; generate-connection-id ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun generate-connection-id ()
"Generate unique ids for use in connections and sripts. (Private)"
(incf *new-id*))
;;;;;;;;;;;;;;;;;;;;
;; get-connection ;;
;;;;;;;;;;;;;;;;;;;;
(defun get-connection (connection-id)
"Return the connection associated with CONNECITION-ID. (Private)"
(gethash connection-id *connection-ids*))
;;;;;;;;;;;;;;;;
;; prep-query ;;
;;;;;;;;;;;;;;;;
(defun prep-query (id default-answer)
"Setup up a query to be received from a script identified by ID an returning
with DEFAULT-ANSWER in case of a time out. (Private)"
(bordeaux-threads:with-lock-held (*queries-lock*)
(setf (gethash id *queries-sems*) (bordeaux-threads:make-semaphore))
(setf (gethash id *queries*) default-answer)))
;;;;;;;;;;;;;;;;;;;;;
;; wait-for-answer ;;
;;;;;;;;;;;;;;;;;;;;;
(defun wait-for-answer (id &key (timeout *query-time-out*))
"Block after prep-query and sending the query script with ID and TIMEOUT with
the default answer. (Private)"
(bordeaux-threads:wait-on-semaphore (gethash id *queries-sems*) :timeout timeout)
(let ((answer (gethash id *queries*)))
(bordeaux-threads:with-lock-held (*queries-lock*)
(remhash id *queries*)
(remhash id *queries-sems*))
answer))
;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; handle-new-connection ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun handle-new-connection (connection id)
(cond (id
(when *verbose-output*
(format t "Reconnection id - ~A to ~A~%" id connection))
(bordeaux-threads:with-lock-held (*connection-lock*)
(setf (gethash id *connection-ids*) connection)
(setf (gethash connection *connections*) id)))
(t
(setf id (generate-connection-id))
(bordeaux-threads:with-lock-held (*connection-lock*)
(setf (gethash connection *connections*) id)
(setf (gethash id *connection-ids*) connection))
(when *verbose-output*
(format t "New connection id - ~A - ~A~%" id connection))
(websocket-driver:send connection
(format nil "clog['connection_id']=~A" id))
(bordeaux-threads:make-thread
(lambda ()
(funcall *on-connect-handler* id))))))
;;;;;;;;;;;;;;;;;;;;
;; handle-message ;;
;;;;;;;;;;;;;;;;;;;;
(defun handle-message (connection message)
(let ((id (gethash connection *connections*))
(ml (ppcre:split ":" message :limit 2)))
(cond ((equal (car ml) "0")
(when *verbose-output*
(format t "~A Ping ~A~%" id (car ml))))
(t
(when *verbose-output*
(format t "~A ~A = ~A~%" id (car ml) (cadr ml)))
(bordeaux-threads:with-lock-held (*queries-lock*)
(setf (gethash (parse-integer (car ml)) *queries*) (cadr ml)))
(bordeaux-threads:signal-semaphore
(gethash (parse-integer (car ml)) *queries-sems*))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; handle-close-connection ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun handle-close-connection (connection)
(let ((id (gethash connection *connections*)))
(when id
(when *verbose-output*
(format t "Connection id ~A has closed. ~A~%" id connection))
(bordeaux-threads:with-lock-held (*connection-lock*)
(remhash id *connection-ids*)
(remhash connection *connections*)))))
;;;;;;;;;;;;;;;;;
;; clog-server ;;
;;;;;;;;;;;;;;;;;
(defun clog-server (env)
(let ((ws (websocket-driver:make-server env)))
(websocket-driver:on :open ws
(lambda ()
(let ((id (getf env :query-string)))
(when (typep id 'string)
(setf id (parse-integer id)))
(handle-new-connection ws id))))
(websocket-driver:on :message ws
(lambda (msg) (handle-message ws msg)))
(websocket-driver:on :close ws
(lambda (&key code reason)
(declare (ignore code reason))
(handle-close-connection ws)))
(lambda (responder)
(declare (ignore responder))
(websocket-driver:start-connection ws))))
;;;;;;;;;;;;;;;;
;; initialize ;;
@ -199,55 +78,26 @@ the default answer. (Private)"
"Inititalze CLOG on a socket using HOST and PORT to serve BOOT-FILE as
the default route to establish web-socket connections and static files
located at STATIC-ROOT."
(set-on-connect on-connect-handler)
(setf *app*
(lack:builder
(:static :path (lambda (path)
(cond ((ppcre:scan "^(?:/clog$)" path) nil)
((equal path "/") boot-file)
(t path)))
:root static-root)
(lambda (env)
(clog-server env))))
(setf *client-handler* (clack:clackup *app* :address host :port port))
(when *verbose-output*
(format t "HTTP listening on : ~A:~A~%" host port)
(format t "HTML Root : ~A~%" static-root)
(format t "Boot file default : ~A~%" boot-file)))
(clog-connection:initialize on-connect-handler
:host host
:port port
:boot-file boot-file
:static-root static-root))
;;;;;;;;;;;;;;
;; shutdown ;;
;;;;;;;;;;;;;;
(defun shutdown ()
"Shutdown CLOG."
(clack:stop *client-handler*)
(bordeaux-threads:with-lock-held (*connection-lock*)
(clrhash *connections*)
(clrhash *connection-ids*))
(setf *app* nil)
(setf *client-handler* nil))
(clog-connection:shutdown))
;;;;;;;;;;;;;;;;;;;;
;; set-on-connect ;;
;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;
;; alert-box ;;
;;;;;;;;;;;;;;;
(defun set-on-connect (on-connect-handler)
"Change the ON-CONNECTION-HANDLER set during Initialize."
(setf *on-connect-handler* on-connect-handler))
;;;;;;;;;;;;;;;;;;;
;; escape-string ;;
;;;;;;;;;;;;;;;;;;;
(defun escape-string (str)
"Escape STR for sending to browser script."
(let ((res))
(setf res (ppcre:regex-replace-all "\\x22" str "\\x22"))
(setf res (ppcre:regex-replace-all "\\x27" res "\\x27"))
(setf res (ppcre:regex-replace-all "\\x0A" res "\\x0A"))
(setf res (ppcre:regex-replace-all "\\x0D" res "\\x0D"))
res))
(defun alert-box (id message)
(clog-connection:execute
id (format nil "alert('~A');" (clog-connection:escape-string message))))
;;;;;;;;;;;;;;;;;;
;; open-browser ;;