On Fri, 2021-12-31 at 15:48 +1100, Duncan Bayne wrote:
Chun Tian writes:
The difference here is that now (lambda (stream) ...) is a closure which will contain a local version of *foo* at the time when (create-server) is called. This kind of uses of lambda functions is like a cheap object with a member variable.
Thanks for the suggestion - yes, this does work as expected, but introduces a difficulty with the API of the library.
The germinal code is as follows (edited to remove large swathes and just focus on the relevant bits):
===== (defvar *germinal-cert* "/etc/germinal/cert.pem") (defvar *germinal-cert-key* "/etc/germinal/key.pem") (defvar *germinal-tls-context* nil "Variable used to store global TLS context")
;; snip
(with-global-context (*germinal-tls-context* :auto-free-p (not background)) (usocket:socket-server host port #'gemini-handler () :multi-threading t :element-type '(unsigned-byte 8) :in-new-thread background)))
;; snip
(defun gemini-handler (stream) "The main Gemini request handler. Sets up TLS and sets up request and response" (handler-case (let* ((tls-stream (make-ssl-server-stream stream :certificate *germinal-cert* :key *germinal-cert- key*)) ;; snip =====
So replacing the handler function with a lambda that creates a closure works ... but breaks the non-testing case where you just want to setq the special variables in your app startup and be done with it.
The best approach I can think of is something like ...
===== ;; snip (with-global-context (*germinal-tls-context* :auto-free-p (not background)) (usocket:socket-server host port (let ((*threaded-cert* *germinal-cert*) (*threaded-cert-key* *germinal-cert- key*)) (lambda (stream) (gemini-handler stream))) ;; snip (let* ((tls-stream (make-ssl-server-stream stream :certificate *threaded-cert* :key *threaded-cert- key*)) ;; snip =====
Which seems weird, but also gives the best of both worlds; the ability to shadow variables for testing purposes, but also setq the *same* variables for global configuration.
Thoughts / opinions?
You're overusing special variables. You should also make up your mind on the concurrency model and try to avoid allowing both foreground and background operations.
(defun gemini-handler (stream cert key) "The main Gemini request handler. Sets up TLS and sets up request and response" (handler-case (let* ((tls-stream (make-ssl-server-stream stream :certificate cert :key key))))))
(defun make-gemini-handler (cert key) (lambda (stream) (gemini-handler stream cert key)))
(with-global-context (*germinal-tls-context* :auto-free-p (not background)) (usocket:socket-server host port (make-gemini-handler *germinal-cert* *germinal-cert-key*)))