Skip to content

Commit

Permalink
feat(cache): test and cache implementation for handling vary header.
Browse files Browse the repository at this point in the history
  • Loading branch information
IsakT committed Jul 10, 2024
1 parent 473085c commit 739a538
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 244 deletions.
153 changes: 25 additions & 128 deletions lib/interceptor/cache.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import assert from 'node:assert'
import { LRUCache } from 'lru-cache'
// import { LRUCache } from 'lru-cache'
import { DecoratorHandler, parseHeaders, parseCacheControl } from '../utils.js'

class CacheHandler extends DecoratorHandler {
Expand All @@ -17,18 +17,12 @@ class CacheHandler extends DecoratorHandler {
}

onConnect(abort) {
console.log('onConnect abort')
console.log(abort)

this.#value = null

return this.#handler.onConnect(abort)
}

onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
console.log('onHeaders')
console.log({ statusCode, rawHeaders, resume, statusMessage, headers })

if (statusCode !== 307) {
return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers)
}
Expand All @@ -39,22 +33,6 @@ class CacheHandler extends DecoratorHandler {
const contentLength = headers['content-length'] ? Number(headers['content-length']) : Infinity
const maxEntrySize = this.#store.maxEntrySize ?? Infinity

console.log({ cacheControl, contentLength, maxEntrySize })

console.log('onHeaders if statement match:')

console.log(
contentLength < maxEntrySize &&
cacheControl &&
cacheControl.public &&
!cacheControl.private &&
!cacheControl['no-store'] &&
!cacheControl['no-cache'] &&
!cacheControl['must-understand'] &&
!cacheControl['must-revalidate'] &&
!cacheControl['proxy-revalidate'],
)

if (
contentLength < maxEntrySize &&
cacheControl &&
Expand All @@ -73,8 +51,6 @@ class CacheHandler extends DecoratorHandler {
? 31556952 // 1 year
: Number(maxAge)

console.log({ ttl, maxAge, cacheControl, contentLength, maxEntrySize })

if (ttl > 0) {
this.#value = {
data: {
Expand All @@ -91,13 +67,8 @@ class CacheHandler extends DecoratorHandler {
ttl: ttl * 1e3,
}
}

console.log({ thisvalue: this.#value })
}

console.log('onHeaders, finish:')
console.log({ statusCode, rawHeaders, resume, statusMessage, headers })

return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers)
}

Expand All @@ -116,33 +87,24 @@ class CacheHandler extends DecoratorHandler {
}

onComplete(rawTrailers) {
console.log('onComplete this:')
console.log({ thisvalue: this.#value })
console.log({ thisstore: this.#store }) // CacheStore{}
console.log({ thishandler: this.#handler }) // RequestHandler{}
console.log({ thishandlervalue: this.#handler.value })
console.log({ this: this })
if (this.#value) {
this.#value.data.rawTrailers = rawTrailers
this.#value.size += rawTrailers?.reduce((xs, x) => xs + x.length, 0) ?? 0
this.#value.size = this.#value.size
? this.#value.size + rawTrailers?.reduce((xs, x) => xs + x.length, 0)
: 0

const opts = this.#handler.opts
const entries = this.#handler.entries
console.log('onComplete this:')
console.log({ opts, entries })

const reqHeaders = this.#handler.opts
const resHeaders = parseHeaders(this.#value.data.rawHeaders)

const vary = formatVaryData(resHeaders, reqHeaders)
this.#value.vary = formatVaryData(resHeaders, reqHeaders)

console.log({ vary })
entries.push(this.#value)

this.#value.vary = vary
sortEntriesByVary(entries)

console.log({ entries })

this.#store.set(this.#key, entries.push(this.#value))
this.#store.set(this.#key, entries)
}
return this.#handler.onComplete(rawTrailers)
}
Expand All @@ -153,45 +115,45 @@ function formatVaryData(resHeaders, reqHeaders) {
?.split(',')
.map((key) => key.trim().toLowerCase())
.map((key) => [key, reqHeaders[key]])
.filter(([_key, val]) => val)
}

// TODO (fix): Async filesystem cache.
class CacheStore {
constructor({ maxSize = 1024 * 1024, maxEntrySize = 128 * 1024 }) {
this.maxSize = maxSize
this.maxEntrySize = maxEntrySize
this.cache = new LRUCache({ maxSize })
this.cache = new Map()
}

set(key, value, opts) {
this.cache.set(key, value, opts)
this.cache.set(key, value)
}

get(key) {
return this.cache.get(key)
}
}

function findEntryByHeaders(entries, reqHeaders) {
// Sort entries by number of vary headers in descending order, because
// we want to compare the most complex response to the request first.
/*
Sort entries by number of vary headers in descending order, because
we need to compare the most complex response to the request first.
A cached response with an empty ´vary´ field will otherwise win every time.
*/
function sortEntriesByVary(entries) {
entries.sort((a, b) => {
const lengthA = a.vary ? a.vary.length : 0
const lengthB = b.vary ? b.vary.length : 0
return lengthB - lengthA
})
}

console.log('Sort entries')
console.log({ entries })

console.log('reqHeaders')
console.log({ reqHeaders })
function findEntryByHeaders(entries, reqHeaders) {
sortEntriesByVary(entries)

return entries?.find(
(entry) =>
entry.vary?.every(([key, val]) => {
console.log(`reqHeaders[${key}] === ${val}`)
console.log({ reqHeadersval: reqHeaders[key] })
return reqHeaders[key] === val
}) ?? true,
)
Expand All @@ -200,13 +162,6 @@ function findEntryByHeaders(entries, reqHeaders) {
const DEFAULT_CACHE_STORE = new CacheStore({ maxSize: 128 * 1024, maxEntrySize: 1024 })

export default (opts) => (dispatch) => (opts, handler) => {
console.log('cache dispatcher:')
console.log(dispatch)
console.log('opts:')
console.log(opts)
console.log('handler:')
console.log(handler)

if (!opts.cache || opts.upgrade) {
return dispatch(opts, handler)
}
Expand Down Expand Up @@ -235,6 +190,8 @@ export default (opts) => (dispatch) => (opts, handler) => {
// Dump body...
opts.body?.on('error', () => {}).resume()

opts.host = opts.host ?? new URL(opts.origin).host

const store = opts.cache === true ? DEFAULT_CACHE_STORE : opts.cache

if (!store) {
Expand All @@ -250,70 +207,10 @@ export default (opts) => (dispatch) => (opts, handler) => {
entries = store.get(key)
}

// testing
const rawHeaders = [
Buffer.from('Content-Type'),
Buffer.from('application/json'),
Buffer.from('Content-Length'),
Buffer.from('10'),
Buffer.from('Cache-Control'),
Buffer.from('public'),
]
// // cannot get the cache to work inside the test, so I hardcode the entries here
entries = [
{
statusCode: 200,
statusMessage: '',
rawHeaders,
rawTrailers: ['Hello'],
body: ['asd1'],
vary: [
['Accept', 'application/xml'],
['User-Agent', 'Mozilla/5.0'],
],
},
{
statusCode: 200,
statusMessage: '',
rawHeaders,
rawTrailers: ['Hello'],
body: ['asd2'],
vary: [
['Accept', 'application/txt'],
['User-Agent', 'Chrome'],
['origin2', 'www.google.com/images'],
],
},
// {
// statusCode: 200, statusMessage: 'last', rawHeaders, rawTrailers: ['Hello'], body: ['asd3'],
// vary: null },
{
statusCode: 200,
statusMessage: 'first',
rawHeaders,
rawTrailers: ['Hello'],
body: ['asd4'],
vary: [
['Accept', 'application/json'],
['User-Agent', 'Mozilla/5.0'],
['host2', 'www.google.com'],
['origin2', 'www.google.com/images'],
],
},
]

// *testing

// Find an entry that matches the request, if any
const entry = findEntryByHeaders(entries, opts)

console.log('Entry found:')
console.log({ entry })

// handler.value.vary = 'foobar'

if (entry) {
const { statusCode, statusMessage, rawHeaders, rawTrailers, body } = entry
const { statusCode, statusMessage, rawHeaders, rawTrailers, body } = entry.data
const ac = new AbortController()
const signal = ac.signal

Expand Down Expand Up @@ -345,8 +242,8 @@ export default (opts) => (dispatch) => (opts, handler) => {

return true
} else {
// handler.opts = opts
// handler.entries = entries
handler.opts = opts
handler.entries = entries
return dispatch(opts, new CacheHandler({ handler, store, key }))
}
}
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@
"prepare": "husky install",
"prepublishOnly": "pinst --disable",
"postpublish": "pinst --enable",
"test": "tap test",
"taprun": "tap run"
"test": "tap test"
},
"lint-staged": {
"*.{js,jsx,md,ts}": [
Expand Down
Loading

0 comments on commit 739a538

Please sign in to comment.