df3bfbd4 |
(defun request (method path out-stream)
(with-line (out-stream)
(format out-stream "~a ~a HTTP/1.1" method path))
out-stream)
(defun header (name value out-stream)
(with-line (out-stream)
(format out-stream "~a: ~a" name value))
out-stream)
(defun body (value out-stream)
(end-line out-stream)
(etypecase value
(stream (loop with buffer = (make-string 1000)
for end-read = (read-sequence buffer value)
while (/= end-read 0)
do (write-sequence buffer out-stream :end end-read)))
(string (write-sequence value out-stream))))
(defmacro define-header-function (name header)
(alexandria:once-only (header)
`(defun ,name (value out-stream)
(header ,header value out-stream))))
(defmacro define-header-functions (() &body definitions)
`(progn ,@(mapcar (fw.lu:destructuring-lambda ((name header))
`(define-header-function ,name ,header))
definitions)))
(define-header-functions ()
(content-type "Content-Type")
(host "Host")
(accept "Accept")
(pragma "Pragma")
(user-agent "User-Agent")
(cache-control "Cache-Control")
(accept-encoding "Accept-Encoding")
(accept-language "Accept-Language"))
(defmacro /> (name (&rest args) &body funs)
(alexandria:with-gensyms (out)
`(defun ,name (,@args ,out)
,@(mapcar (serapeum:op `(,@_ ,out))
funs))))
(/> build-subreddit-request (method subreddit)
(request method (format nil "/r/~a.rss" subreddit))
(accept "text/html")
(accept-language "en-US,en;q=0.8")
(cache-control "no-cache")
(host "www.reddit.com")
(pragma "no-cache")
(header "Upgrade-Insecure-Requests" "1")
(user-agent "foobar 0.0")
(body ""))
(defun read-in-header (stream)
(values (read-line stream)
(loop with headers = ()
for line = (read-line stream nil)
while line
until (string= line "
")
do
(push (map 'list 'metatilities:strip-whitespace
(fwoar.string-utils:split #\: (metatilities:strip-whitespace line) :count 2))
headers)
finally
(return (nreverse headers)))))
(defmacro with-subreddit-request ((subreddit stream) &body body)
(alexandria:with-gensyms (socket sock-stream ssl-stream)
`(usocket:with-client-socket (,socket ,sock-stream "reddit.com" 443 :element-type '(unsigned-byte 8))
(let* ((,ssl-stream (cl+ssl:make-ssl-client-stream ,sock-stream :hostname "reddit.com"))
(,stream (flexi-streams:make-flexi-stream ,ssl-stream :external-format :iso-8859-1)))
(build-subreddit-request :GET ,subreddit ,stream)
(finish-output ,stream)
(unwind-protect (progn ,@body)
(close ,ssl-stream))))))
(defun get-header (name headers)
(cadr (assoc name headers :test 'equalp)))
(defun get-content-length (headers)
(parse-integer (get-header "Content-Length" headers)))
(defmacro with-parsed-response (char-stream (result headers reply-buf content-length) &body body)
(alexandria:once-only (char-stream)
`(multiple-value-bind (,result ,headers) (read-in-header ,char-stream)
(let* ((,reply-buf (make-string (get-content-length ,headers)))
(,content-length (read-sequence ,reply-buf ,char-stream)))
,@body))))
(defun get-subreddit-feed (&optional (subreddit "lisp"))
(with-subreddit-request (subreddit char-stream)
(with-parsed-response char-stream (result headers reply-buf content-length)
(values reply-buf
result
headers
content-length))))
|