Hi, Racket Discourse.
I realized last night that I could serialize the responses provided by http-easy
with minimal effort.
This is awesome, because it allows one to essentially cache-and-replay the responses from your requests. Of course, this isn't super novel, but I had never gotten to the point where the realization kicked in, until now.
I have a suspicion I've seen a blog post about this before, but for the life of me, I cannot recall the specifics, if I did.
#lang racket/base
(require
net/http-easy
(only-in
racket/list remf)
(only-in
json read-json jsexpr->string jsexpr?)
(only-in
json/pretty pretty-print-json))
(provide
cache
cached define-cached
reload
persist)
(define cache (make-parameter #false))
(define (response->jsexpr r)
(hasheq
'status (bytes->string/utf-8 (response-status-line r))
'headers (map bytes->string/utf-8 (response-headers r))
'output (bytes->string/utf-8 (response-body r))
'history (map response->jsexpr (response-history r))))
(define (jsexpr->response js)
(make-response
(string->bytes/utf-8 (hash-ref js 'status))
(map string->bytes/utf-8 (hash-ref js 'headers))
(open-input-bytes (string->bytes/utf-8 (hash-ref js 'output)))
(map jsexpr->response (hash-ref js 'history))
void))
(define ((app/rsp f) r)
(hash-set r 'rsp (f (hash-ref r 'rsp))))
(define (search key section)
(define (key? rsp) (equal? key (hash-ref rsp 'key)))
(values
(findf key? section) (remf key? section)))
(define (cached name request . key)
(unless (jsexpr? key)
(error 'cached "expected a jsexpr?, found: ~a" key))
(define sec (hash-ref {cache} name null))
(define-values (val rst) (search key sec))
(cond
[(not val)
(define response (request))
{cache
(hash-set
{cache} name (cons (hasheq 'key key 'rsp response) rst))}
response]
[else
(hash-ref val 'rsp)]))
(define-syntax-rule
(define-cached (name args ...)
#:key [key ...] body ...)
#;becomes
(define (name args ...)
(cached 'name (lambda () body ...) key ...)))
(define (reload)
(cond
[(not (file-exists? "cache.json"))
(hasheq)]
[else
(with-input-from-file "cache.json"
(lambda ()
(for/hasheq ([(key val) (in-immutable-hash (read-json))])
(values key (map (app/rsp jsexpr->response) val)))))]))
(define (persist)
(with-output-to-file "cache.json"
#:exists 'truncate/replace
(lambda ()
(pretty-print-json
(jsexpr->string
(for/hasheq ([(key val) (in-immutable-hash {cache})])
(values key (map (app/rsp response->jsexpr) val))))))))
Defining a cached procedure looks like this, for example:
(define-cached (get-user-details [user #false])
#:key [user]
(get "https://gitea-server.co.za/api/v1/user"
#:auth (bearer-auth gitea-token)
#:headers
(if (not user)
(hasheq)
(hasheq 'sudo user))))
Now, my scribble documents only generate slowly on cache-reload, and so much faster when not loading fresh data from the API.