Dynamic cache length overriding (#2755)

For the Endpoint badge: #2473.

`request-handler.js` is such a bear. I’m looking forward to being able to rewrite it when the service refactor is done.
This commit is contained in:
Paul Melnikow
2019-01-15 15:44:39 -05:00
committed by GitHub
parent a2eec8c8ec
commit cab689706f
7 changed files with 217 additions and 45 deletions

View File

@@ -89,19 +89,35 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
const allowedKeys = flattenQueryParams(handlerOptions.queryParams)
const {
cacheLength: serviceCacheLengthSeconds,
cacheLength: serviceDefaultCacheLengthSeconds,
fetchLimitBytes,
} = handlerOptions
return (queryParams, match, end, ask) => {
const reqTime = new Date()
setCacheHeaders({
cacheHeaderConfig,
serviceCacheLengthSeconds,
queryParams,
res: ask.res,
})
// `defaultCacheLengthSeconds` can be overridden by
// `serviceDefaultCacheLengthSeconds` (either by category or on a badge-
// by-badge basis). Then in turn that can be overridden by
// `serviceOverrideCacheLengthSeconds` (which we expect to be used only in
// the dynamic badge) but only if `serviceOverrideCacheLengthSeconds` is
// longer than `serviceDefaultCacheLengthSeconds` and then the `maxAge`
// query param can also override both of those but again only if `maxAge`
// is longer.
//
// When the legacy services have been rewritten, all the code in here
// will go away, which should achieve this goal in a simpler way.
//
// Ref: https://github.com/badges/shields/pull/2755
function setCacheHeadersOnResponse(res, serviceOverrideCacheLengthSeconds) {
setCacheHeaders({
cacheHeaderConfig,
serviceDefaultCacheLengthSeconds,
serviceOverrideCacheLengthSeconds,
queryParams,
res,
})
}
analytics.noteRequest(queryParams, match)
@@ -123,6 +139,10 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
const tooSoon = +reqTime - cached.time < cached.interval
if (tooSoon || cached.dataChange / cached.reqs <= freqRatioMax) {
const svg = makeBadge(cached.data.badgeData)
setCacheHeadersOnResponse(
ask.res,
cached.data.badgeData.cacheLengthSeconds
)
makeSend(cached.data.format, ask.res, end)(svg)
cachedVersionSent = true
// We do not wish to call the vendor servers.
@@ -140,9 +160,13 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
return
}
if (requestCache.has(cacheIndex)) {
const cached = requestCache.get(cacheIndex).data
const svg = makeBadge(cached.badgeData)
makeSend(cached.format, ask.res, end)(svg)
const cached = requestCache.get(cacheIndex)
const svg = makeBadge(cached.data.badgeData)
setCacheHeadersOnResponse(
ask.res,
cached.data.badgeData.cacheLengthSeconds
)
makeSend(cached.data.format, ask.res, end)(svg)
return
}
ask.res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
@@ -155,6 +179,7 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
extension = 'svg'
}
const svg = makeBadge(badgeData)
setCacheHeadersOnResponse(ask.res)
makeSend(extension, ask.res, end)(svg)
}, 25000)
@@ -256,6 +281,7 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
requestCache.set(cacheIndex, updatedCache)
if (!cachedVersionSent) {
const svg = makeBadge(badgeData)
setCacheHeadersOnResponse(ask.res, badgeData.cacheLengthSeconds)
makeSend(format, ask.res, end)(svg)
}
},

View File

@@ -25,6 +25,16 @@ function fakeHandler(queryParams, match, sendBadge, request) {
sendBadge(format, badgeData)
}
function createFakeHandlerWithCacheLength(cacheLengthSeconds) {
return function fakeHandler(queryParams, match, sendBadge, request) {
const [, someValue, format] = match
const badgeData = getBadgeData('testing', queryParams)
badgeData.text[1] = someValue
badgeData.cacheLengthSeconds = cacheLengthSeconds
sendBadge(format, badgeData)
}
}
function fakeHandlerWithNetworkIo(queryParams, match, sendBadge, request) {
const [, someValue, format] = match
request('https://www.google.com/foo/bar', (err, res, buffer) => {
@@ -198,6 +208,62 @@ describe('The request handler', function() {
expect(res.headers.get('cache-control')).to.equal('max-age=900')
})
it('should set the expected cache headers on cached responses', async function() {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 900 } })
// Make first request.
await fetch(`${baseUrl}/testing/123.json`)
const res = await fetch(`${baseUrl}/testing/123.json`)
const expectedExpiry = new Date(
+new Date(res.headers.get('date')) + 900000
).toGMTString()
expect(res.headers.get('expires')).to.equal(expectedExpiry)
expect(res.headers.get('cache-control')).to.equal('max-age=900')
})
it('should let live service data override the default cache headers with longer value', async function() {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(
{ defaultCacheLengthSeconds: 300 },
(queryParams, match, sendBadge, request) => {
++handlerCallCount
createFakeHandlerWithCacheLength(400)(
queryParams,
match,
sendBadge,
request
)
}
)
)
const res = await fetch(`${baseUrl}/testing/123.json`)
expect(res.headers.get('cache-control')).to.equal('max-age=400')
})
it('should not let live service data override the default cache headers with shorter value', async function() {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(
{ defaultCacheLengthSeconds: 300 },
(queryParams, match, sendBadge, request) => {
++handlerCallCount
createFakeHandlerWithCacheLength(200)(
queryParams,
match,
sendBadge,
request
)
}
)
)
const res = await fetch(`${baseUrl}/testing/123.json`)
expect(res.headers.get('cache-control')).to.equal('max-age=300')
})
it('should set the expires header to current time + maxAge', async function() {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 0 } })
const res = await fetch(`${baseUrl}/testing/123.json?maxAge=3600`)