commit 321ea045f067bea4b30421897082ccd0ea25bbb2 from: aartaka via: omar-polo date: Mon Jan 17 11:21:05 2022 UTC gemini: Add with-gemini-request macro. This also includes a minor refactoring of the code to be less duplicative and rely on multiple values instead of list return values. commit - a903e42230f413b43c0115c69fc044d4b1c8b3b4 commit + 321ea045f067bea4b30421897082ccd0ea25bbb2 blob - 5d4aef8fe2217688c34bc1f47d8c6d89250e3d0c blob + 240c7e1c5dd5133657586de86f1dba6ab1a3a284 --- gemini.lisp +++ gemini.lisp @@ -47,7 +47,7 @@ (destructuring-bind (status &optional meta) (cl-ppcre:split "\\s+" res :limit 2) (when (and (< (parse-integer status) 40) (not meta)) (error 'malformed-response :reason "missing meta")) - (list (parse-status status) meta))) + (values (parse-status status) meta))) (defun read-all-string (in) (with-output-to-string (out) @@ -76,32 +76,38 @@ do (write-char ch out)) (write-char char out))) -(defun do-request (host port req) - "Perform the request REQ to HOST on PORT, blocking until the -response is fetched, then return the meta and the (decoded) body." - (usocket:with-client-socket (socket stream host port) - (let ((ssl-stream (cl+ssl:make-ssl-client-stream - stream :unwrap-stream-p t - :external-format '(:utf8 :eol-style :lf) - :verify nil - :hostname host))) - (format ssl-stream "~a~c~c" req #\return #\newline) - (force-output ssl-stream) - (let ((resp (parse-response (read-until ssl-stream #\newline)))) - (values resp (if (and (eq (first resp) :success) - (second resp) - (string= (subseq (second resp) 0 5) "text/")) - (read-all-string ssl-stream) - (read-all-bytes ssl-stream))))))) +(defmacro with-gemini-request (((status meta stream) url) &body body) + "Expose a stream (STREAM) with Gemini response contents, available in BODY. -(defgeneric request (url) - (:documentation "Perform a request for the URL")) +STATUS and META are bound to the status code (as keyword from +`*code-to-keyword*') and meta info (as optional/nullable string.) -(defmethod request ((url string)) - (request (quri:uri url))) +URL should be a well-formed string/`quri:uri' URL." + (let* ((socket-var (gensym "SOCKET")) + (socket-stream-var (gensym "SOCKET-STREAM")) + (host-var (gensym "HOST")) + (port-var (gensym "PORT")) + (url-var (gensym "URL"))) + `(let* ((,url-var (quri:uri ,url)) + (,host-var (quri:uri-host ,url-var)) + (,port-var (or (quri:uri-port ,url-var) phos/gemini:*default-port*))) + (usocket:with-client-socket (,socket-var ,socket-stream-var ,host-var ,port-var) + (let ((,stream (cl+ssl:make-ssl-client-stream + ,socket-stream-var :unwrap-stream-p t + :external-format '(:utf8 :eol-style :lf) + :verify nil + :hostname ,host-var))) + (format ,stream "~a~c~c" (quri:render-uri ,url-var) #\return #\newline) + (force-output ,stream) + (multiple-value-bind (,status ,meta) + (parse-response (read-until ,stream #\newline)) + ,@body)))))) -(defmethod request ((url quri:uri)) - (let* ((u (quri:uri url)) - (port (or (quri:uri-port u) 1965)) - (host (quri:uri-host u))) - (do-request host port url))) +(defgeneric request (url) + (:method (url) + (with-gemini-request ((status meta stream) url) + (values status meta (if (and (eq status :success) + meta (string= (subseq meta 0 5) "text/")) + (read-all-string stream) + (read-all-bytes stream))))) + (:documentation "Perform a request for the URL.")) blob - 5e7d00632ccd96b9f5c3965433a154c3c58cc3c2 blob + 909c7eb58147e64e2002e5818918fca141be047c --- package.lisp +++ package.lisp @@ -16,4 +16,4 @@ (:documentation "Gemini (the protocol) implementation") (:nicknames :gemini) (:use #:cl #:trivia) - (:export :request)) + (:export :request :with-gemini-request :*default-port* :*code-to-keyword*))