mirror of
https://github.com/rabbibotton/clog.git
synced 2025-12-05 18:20:36 -08:00
207 lines
6.4 KiB
Common Lisp
207 lines
6.4 KiB
Common Lisp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;;; CLOG - The Common Lisp Omnificent GUI ;;;;
|
|
;;;; (c) 2020-2021 David Botton ;;;;
|
|
;;;; License BSD 3 Clause ;;;;
|
|
;;;; ;;;;
|
|
;;;; clog.lisp ;;;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Exports - clog
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(mgl-pax:define-package :clog
|
|
(:documentation "The Common List Omnificent GUI - Parent package")
|
|
(:use #:cl #:mgl-pax))
|
|
|
|
(in-package :clog)
|
|
|
|
(defsection @clog-manual (:title "The CLOG manual")
|
|
"The Common Lisp Omnificient GUI, CLOG for short, uses web technology
|
|
to produce graphical user interfaces for applications locally or
|
|
remotely. The CLOG package starts up the connectivity to the browser
|
|
or other websocket client (often a browser embedded in a native
|
|
application."
|
|
|
|
(clog asdf:system)
|
|
|
|
(@clog-top-level section))
|
|
|
|
(defsection @clog-top-level (:title "CLOG Top level")
|
|
"CLOG system startup and shutdown"
|
|
|
|
(*verbose-output* variable)
|
|
|
|
(initialize function)
|
|
(shutdown function)
|
|
(set-on-connect function)
|
|
|
|
"CLOG utilities"
|
|
|
|
(open-browser function))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; 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")
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; generate-connection-id ;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defun generate-connection-id ()
|
|
(incf *new-id*))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
;; get-connection ;;
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defun get-connection (connection-id)
|
|
"Return the connection associated with CONNECITION-ID. (Private)"
|
|
(gethash connection-id *connection-ids*))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; 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*)))
|
|
(format t "msg: ~A sent ~A - ~A~%" id message connection)))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; 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 ;;
|
|
;;;;;;;;;;;;;;;;
|
|
|
|
(defun initialize (on-connect-handler
|
|
&key
|
|
(host "0.0.0.0")
|
|
(port 8080)
|
|
(boot-file "/boot.html")
|
|
(static-root #P"./static-files/"))
|
|
"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*
|
|
(progn
|
|
(format t "HTTP listening on : ~A:~A~%" host port)
|
|
(format t "HTML Root : ~A~%" static-root)
|
|
(format t "Boot file default : ~A~%" boot-file))))
|
|
|
|
;;;;;;;;;;;;;;
|
|
;; 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))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
;; set-on-connect ;;
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defun set-on-connect (on-connect-handler)
|
|
"Change the ON-CONNECTION-HANDLER set during Initialize."
|
|
(setf *on-connect-handler* on-connect-handler))
|
|
|
|
;;;;;;;;;;;;;;;;;;
|
|
;; open-browser ;;
|
|
;;;;;;;;;;;;;;;;;;
|
|
|
|
(defun open-browser (&key (url "http://127.0.0.1:8080"))
|
|
"Open a web browser to URL."
|
|
(trivial-open-browser:open-browser url))
|