Files
herolib/lib/core/httpconnection/caching.v
2024-12-25 10:18:31 +01:00

101 lines
2.8 KiB
V

module httpconnection
import crypto.md5
import json
import net.http { Method }
// https://cassiomolin.com/2016/09/09/which-http-status-codes-are-cacheable/
const default_cacheable_codes = [200, 203, 204, 206, 300, 404, 405, 410, 414, 501]
const unsafe_http_methods = [Method.put, .patch, .post, .delete]
pub struct CacheConfig {
pub mut:
key string // as used to identity in redis
allowable_methods []Method = [.get, .head]
allowable_codes []int = default_cacheable_codes
disable bool = true // default cache is not working
expire_after int = 3600 // default expire_after is 1h
match_headers bool // cache the request header to be matched later
}
pub struct Result {
pub mut:
code int
data string
}
// calculate the key for the cache starting from data and url
fn (mut h HTTPConnection) cache_key(req Request) string {
url := h.url(req).split('!')
encoded_url := md5.hexhash(url[0]) // without params
mut key := 'http:${h.cache.key}:${req.method}:${encoded_url}'
mut req_data := req.data
if h.cache.match_headers {
req_data += json.encode(h.header())
}
req_data += if url.len > 1 { url[1] } else { '' } // add url param if exist
key += if req_data.len > 0 { ':${md5.hexhash(req_data)}' } else { '' }
return key
}
// Get request result from cache, return -1 if missed.
fn (mut h HTTPConnection) cache_get(req Request) !Result {
key := h.cache_key(req)
mut data := h.redis.get(key) or {
assert '${err}' == 'none'
// console.print_debug("cache get: ${key} not in redis")
return Result{
code: -1
}
}
if data == '' {
// console.print_debug("cache get: ${key} empty data")
return Result{
code: -1
}
}
result := json.decode(Result, data) or {
// console.print_debug("cache get: ${key} coud not decode")
return error('failed to decode result with error: ${err}.\ndata:\n${data}')
}
// console.print_debug("cache get: ${key} ok")
return result
}
// Set response result in cache
fn (mut h HTTPConnection) cache_set(req Request, res Result) ! {
key := h.cache_key(req)
value := json.encode(res)
h.redis.set(key, value)!
h.redis.expire(key, h.cache.expire_after)!
}
// Invalidate cache for specific url
fn (mut h HTTPConnection) cache_invalidate(req Request) ! {
url := h.url(req).split('!')
encoded_url := md5.hexhash(url[0])
mut to_drop := []string{}
to_drop << 'http:${h.cache.key}:*:${encoded_url}*'
if req.id.len > 0 {
url_no_id := url[0].trim_string_right('/${req.id}')
encoded_url_no_id := md5.hexhash(url_no_id)
to_drop << 'http:${h.cache.key}:*:${encoded_url_no_id}*'
}
for pattern in to_drop {
all_keys := h.redis.keys(pattern)!
for key in all_keys {
h.redis.del(key)!
}
}
}
// drop full cache for specific cache_key
pub fn (mut h HTTPConnection) cache_drop() ! {
todrop := 'http:${h.cache.key}*'
all_keys := h.redis.keys(todrop)!
for key in all_keys {
h.redis.del(key)!
}
}