Better cache control when not using CDN

This commit is contained in:
Bobby Wibowo 2020-02-11 17:18:04 +07:00
parent c0fc463595
commit edce59243b
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
3 changed files with 66 additions and 48 deletions

View File

@ -101,7 +101,8 @@ module.exports = {
/* /*
Trust proxy. Trust proxy.
Only enable if you are running this behind a proxy like Cloudflare, Incapsula, etc. Enable this if you are using proxy such as Cloudflare or Incapsula,
and/or also when you are using reverse proxy such as nginx or Apache.
*/ */
trustProxy: true, trustProxy: true,
@ -141,13 +142,13 @@ module.exports = {
} }
}, },
{ {
// 4 requests in 30 seconds // 6 requests in 30 seconds
routes: [ routes: [
'/api/album/zip' '/api/album/zip'
], ],
config: { config: {
windowMs: 30 * 1000, windowMs: 30 * 1000,
max: 4 max: 6
} }
}, },
{ {
@ -449,6 +450,8 @@ module.exports = {
ADVANCED: Use safe.fiery.me-exclusive cache control. ADVANCED: Use safe.fiery.me-exclusive cache control.
This will only work properly with certain settings in nginx reverse proxy and Cloudflare. This will only work properly with certain settings in nginx reverse proxy and Cloudflare.
Do NOT enable unless you know what you are doing. Do NOT enable unless you know what you are doing.
true: With CDN (Cloudflare)
2: When NOT using Cloudflare
*/ */
cacheControl: false, cacheControl: false,

View File

@ -352,14 +352,6 @@ self.get = async (req, res, next) => {
self.generateZip = async (req, res, next) => { self.generateZip = async (req, res, next) => {
const versionString = parseInt(req.query.v) const versionString = parseInt(req.query.v)
const download = (filePath, fileName) => {
const headers = {}
if (config.cacheControl && versionString > 0) {
headers['Access-Control-Allow-Origin'] = '*'
headers['Cache-Control'] = 'public, max-age=2592000, must-revalidate, proxy-revalidate, immutable, stale-while-revalidate=86400, stale-if-error=604800'
}
return res.download(filePath, fileName, { headers })
}
const identifier = req.params.identifier const identifier = req.params.identifier
if (identifier === undefined) if (identifier === undefined)
@ -398,7 +390,7 @@ self.generateZip = async (req, res, next) => {
try { try {
const filePath = path.join(paths.zips, `${identifier}.zip`) const filePath = path.join(paths.zips, `${identifier}.zip`)
await paths.access(filePath) await paths.access(filePath)
return download(filePath, `${album.name}.zip`) return res.download(filePath, `${album.name}.zip`)
} catch (error) { } catch (error) {
// Re-throw error // Re-throw error
if (error.code !== 'ENOENT') if (error.code !== 'ENOENT')
@ -409,7 +401,7 @@ self.generateZip = async (req, res, next) => {
logger.log(`Waiting previous zip task for album: ${identifier}.`) logger.log(`Waiting previous zip task for album: ${identifier}.`)
return self.zipEmitters.get(identifier).once('done', (filePath, fileName, json) => { return self.zipEmitters.get(identifier).once('done', (filePath, fileName, json) => {
if (filePath && fileName) if (filePath && fileName)
download(filePath, fileName) res.download(filePath, fileName)
else if (json) else if (json)
res.json(json) res.json(json)
}) })
@ -481,7 +473,7 @@ self.generateZip = async (req, res, next) => {
const fileName = `${album.name}.zip` const fileName = `${album.name}.zip`
self.zipEmitters.get(identifier).emit('done', filePath, fileName) self.zipEmitters.get(identifier).emit('done', filePath, fileName)
return download(filePath, fileName) return res.download(filePath, fileName)
} catch (error) { } catch (error) {
logger.error(error) logger.error(error)
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' }) return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' })

View File

@ -50,52 +50,76 @@ if (Array.isArray(config.rateLimits) && config.rateLimits.length)
safe.use(bodyParser.urlencoded({ extended: true })) safe.use(bodyParser.urlencoded({ extended: true }))
safe.use(bodyParser.json()) safe.use(bodyParser.json())
let setHeaders let cdnPages
let setHeaders = res => {
res.set('Access-Control-Allow-Origin', '*')
}
// Cache control (safe.fiery.me) // Cache control (safe.fiery.me)
if (config.cacheControl) { if (config.cacheControl) {
const cacheControls = { const cacheControls = {
// max-age: 30 days // max-age: 6 months
default: 'public, max-age=2592000, must-revalidate, proxy-revalidate, immutable, stale-while-revalidate=86400, stale-if-error=604800', static: 'public, max-age=15778800, must-revalidate, proxy-revalidate, immutable, stale-while-revalidate=86400, stale-if-error=604800',
// s-max-age: 30 days (only cache in proxy server) // s-max-age: 6 months (only cache in CDN)
// Obviously we have to purge proxy cache on every update cdn: 's-max-age=15778800, proxy-revalidate, stale-while-revalidate=86400, stale-if-error=604800',
proxyOnly: 's-max-age=2592000, proxy-revalidate, stale-while-revalidate=86400, stale-if-error=604800', // validate cache's validity before using them (soft cache)
validate: 'no-cache',
// do not use cache at all
disable: 'no-store' disable: 'no-store'
} }
// By default soft cache everything
safe.use('/', (req, res, next) => { safe.use('/', (req, res, next) => {
res.set('Cache-Control', cacheControls.proxyOnly) res.set('Cache-Control', cacheControls.validate)
next() next()
}) })
// Do NOT cache these dynamic routes // If using CDN, cache public pages in CDN
safe.use(['/a', '/api', '/nojs'], (req, res, next) => { if (config.cacheControl !== 2) {
res.set('Cache-Control', cacheControls.disable) cdnPages = config.pages.concat(['api/check'])
next() for (const page of cdnPages)
}) safe.use(`/${page === 'home' ? '' : page}`, (req, res, next) => {
res.set('Cache-Control', cacheControls.cdn)
next()
})
}
// Cache these in proxy server though // If serving uploads with node
safe.use(['/api/check'], (req, res, next) => { if (config.serveFilesWithNode)
res.set('Cache-Control', cacheControls.proxyOnly) safe.use('/', express.static(paths.uploads, {
next() setHeaders: res => {
}) res.set('Access-Control-Allow-Origin', '*')
// If using CDN, cache uploads in CDN as well
// Use with cloudflare.purgeCache enabled in config file
if (config.cacheControl !== 2)
res.set('Cache-Control', cacheControls.cdn)
}
}))
// Cache album ZIPs // Function for static assets.
safe.use(['/api/album/zip'], (req, res, next) => { // This requires the assets to use version in their query string,
setHeaders(res) // as they will be cached by clients for a very long time.
next()
})
// For static assets (and uploads if serving with node)
setHeaders = res => { setHeaders = res => {
res.set('Access-Control-Allow-Origin', '*') res.set('Access-Control-Allow-Origin', '*')
res.set('Cache-Control', cacheControls.default) res.set('Cache-Control', cacheControls.static)
} }
// Consider album ZIPs static as well, since they use version in their query string
safe.use(['/api/album/zip'], (req, res, next) => {
res.set('Access-Control-Allow-Origin', '*')
const versionString = parseInt(req.query.v)
if (versionString > 0)
res.set('Cache-Control', cacheControls.static)
else
res.set('Cache-Control', cacheControls.disable)
next()
})
} else if (config.serveFilesWithNode) {
// If serving uploads with node
safe.use('/', express.static(paths.uploads))
} }
if (config.serveFilesWithNode) // Static assets
safe.use('/', express.static(paths.uploads, { setHeaders }))
safe.use('/', express.static(paths.public, { setHeaders })) safe.use('/', express.static(paths.public, { setHeaders }))
safe.use('/', express.static(paths.dist, { setHeaders })) safe.use('/', express.static(paths.dist, { setHeaders }))
@ -142,13 +166,13 @@ safe.use('/api', api)
// Error pages // Error pages
safe.use((req, res, next) => { safe.use((req, res, next) => {
if (config.cacheControl) res.removeHeader('Cache-Control') res.setHeader('Cache-Control', 'no-store')
res.status(404).sendFile(path.join(paths.errorRoot, config.errorPages[404])) res.status(404).sendFile(path.join(paths.errorRoot, config.errorPages[404]))
}) })
safe.use((error, req, res, next) => { safe.use((error, req, res, next) => {
logger.error(error) logger.error(error)
if (config.cacheControl) res.removeHeader('Cache-Control') res.setHeader('Cache-Control', 'no-store')
res.status(500).sendFile(path.join(paths.errorRoot, config.errorPages[500])) res.status(500).sendFile(path.join(paths.errorRoot, config.errorPages[500]))
}) })
@ -196,12 +220,11 @@ safe.use('/api', api)
logger.log(`lolisafe started on port ${config.port}`) logger.log(`lolisafe started on port ${config.port}`)
// Cache control (safe.fiery.me) // Cache control (safe.fiery.me)
// Also only if explicitly using Cloudflare // Purge Cloudflare cache
if (config.cacheControl) if (config.cacheControl === 1)
if (config.cloudflare.purgeCache) { if (config.cloudflare.purgeCache) {
logger.log('Cache control enabled, purging Cloudflare\'s cache...') logger.log('Cache control enabled, purging Cloudflare\'s cache...')
const routes = config.pages.concat(['api/check']) const results = await utils.purgeCloudflareCache(cdnPages)
const results = await utils.purgeCloudflareCache(routes)
let errored = false let errored = false
let succeeded = 0 let succeeded = 0
for (const result of results) { for (const result of results) {