-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The RESTDataSource
class does not take Vary
headers into account by default, and overriding cacheKeyFor
doesn't seem sufficient
#3136
Comments
In case it's of interest, this is the code I have so far for the import { RESTDataSource } from 'apollo-datasource-rest'
import flatten from 'lodash/flatten'
const pluckHeaders = (headers, keyName) =>
Array.from(headers).reduce(
(acc, [key, value]) => (key === keyName ? [...acc, value] : acc),
[]
)
const pluckHeader = (headers, keyName) => {
const pluckedHeaders = pluckHeaders(headers, keyName)
return pluckedHeaders[pluckedHeaders.length - 1]
}
const createCacheKey = (url, headers, varyFields = []) => {
const varyFieldsStr = varyFields.map(h =>
pluckHeader(headers, h.toLowerCase())
)
return varyFieldsStr.length === 0 ? url : url + '::' + varyFieldsStr
}
class VaryCachingRESTDataSource extends RESTDataSource {
static varyFields = {}
static origRequestHeaders = {}
constructor() {
super()
console.log('>>> new VaryCachingRESTDataSource()')
}
cacheKeyFor(request) {
console.log('>>> cacheKeyFor (BEGIN)')
const varyFields = this.constructor.varyFields[request.url]
const cacheKey = createCacheKey(request.url, request.headers, varyFields)
if (varyFields) {
const origRequestHeaders = this.constructor.origRequestHeaders[
request.url
]
const origCacheKey = createCacheKey(
request.url,
origRequestHeaders,
varyFields
)
if (cacheKey === origCacheKey) {
console.log('>>> not returning generated cacheKey', cacheKey)
console.log(
'>>> returning orig cacheKey',
createCacheKey(request.url, origRequestHeaders)
)
return createCacheKey(request.url, origRequestHeaders)
}
} else {
this.constructor.origRequestHeaders[request.url] = request.headers
}
console.log('>>> cacheKeyFor', cacheKey)
return cacheKey
}
cacheOptionsFor(response, request) {
console.log('>>> cacheOptionsFor (BEGIN)')
const varyHeaders = pluckHeaders(response.headers, 'vary')
console.log('>>> varyHeaders', varyHeaders)
const varyFields = flatten(varyHeaders.map(h => h.split(/\s*,\s*/)))
console.log('>>> varyFields', varyFields)
this.constructor.varyFields[request.url] = varyFields
return super.cacheOptionsFor
? super.cacheOptionsFor(response, request)
: null
}
flushCache() {
console.log('>>> flushCache (BEGIN)')
this.httpCache.keyValueCache.store.reset()
this.constructor.varyFields = {}
this.constructor.origRequestHeaders = {}
console.log('>>> flushCache (END)')
}
}
export default VaryCachingRESTDataSource |
So I've just this minute got this working. It turns out that |
Closing as I've now verified that the |
In case it helps anyone else, this is what I ended up with: import { RESTDataSource } from 'apollo-datasource-rest'
const pluckHeaders = (headers, keyName) =>
Array.from(headers).reduce(
(acc, [key, value]) => (key === keyName ? [...acc, value] : acc),
[]
)
const pluckHeader = (headers, keyName) => {
const pluckedHeaders = pluckHeaders(headers, keyName)
return pluckedHeaders[pluckedHeaders.length - 1]
}
const createCacheKey = (url, headers, varyFields = []) => {
const varyFieldsStr = varyFields.map(h =>
pluckHeader(headers, h.toLowerCase())
)
return varyFieldsStr.length === 0 ? url : url + '::' + varyFieldsStr
}
/**
* The `RESTDataSource` class offers caching, but will not cache resource
* variations as defined by the `Vary` header. Instead, it supports the caching
* of a single resource per URL, and will then return this cached resource only
* while clients continue to request the same variety, but will overwrite the
* cache with a different resource variety as soon as that's requested.
*/
class VaryCachingRESTDataSource extends RESTDataSource {
static varyFields = {}
static origRequestHeaders = {}
constructor() {
super()
}
cacheKeyFor(request) {
const varyFields = this.constructor.varyFields[request.url]
const cacheKey = createCacheKey(request.url, request.headers, varyFields)
if (varyFields) {
const origRequestHeaders = this.constructor.origRequestHeaders[
request.url
]
const origCacheKey = createCacheKey(
request.url,
origRequestHeaders,
varyFields
)
if (cacheKey === origCacheKey) {
return createCacheKey(request.url, origRequestHeaders)
}
} else {
this.constructor.origRequestHeaders[request.url] = request.headers
}
return cacheKey
}
cacheOptionsFor(response, request) {
const varyHeaders = pluckHeaders(response.headers, 'vary')
const varyFields = varyHeaders.map(h => h.split(/\s*,\s*/)).flat()
this.constructor.varyFields[request.url] = varyFields
return super.cacheOptionsFor
? super.cacheOptionsFor(response, request)
: null
}
flushCache() {
this.constructor.varyFields = {}
this.constructor.origRequestHeaders = {}
const keyValueCache = this.httpCache.keyValueCache
if (keyValueCache.store) {
keyValueCache.store.reset()
} else if (keyValueCache.client) {
keyValueCache.client.flushdb()
} else {
throw new Error(
'The `flushCache` method only currently works with the built-in and redis caches'
)
}
}
}
export default VaryCachingRESTDataSource |
The documentation for the overridable
cacheKeyFor
method withinRESTDataSource
is as follows:which suggests that implementors could get the
Vary
header fields to be taken into account by including them within the cache key. However, given that the cache is checked before the response is available, we clearly can't act based on the value of theVary
header at this point?There is also a
cacheOptionsFor
method that does receive both therequest
and theresponse
, and I could use this to store the vary fields for future reference, but at present the only metadata from this which are stored along with the cached entity is the overriddenttl
, if one is provided:I decided to try and work around these issues by creating a
VaryCachingRESTDataSource
class that would maintain its own vary field for all cached entities, plus also maintain the original request headers used the first time an entity is cached so that we can still make use of that cached value in the future once we know what what theVary
headers are, if any.Unfortunately, this still didn't work for me, and I've so far been unable to have caching from servers that return
Vary
headers. Is there something I'm overlooking here, or is complete support for theVary
header not currently possible as things stand?The text was updated successfully, but these errors were encountered: