From edce59243bb5a9e44c60b07943d165db421e3a5a Mon Sep 17 00:00:00 2001 From: Bobby Wibowo Date: Tue, 11 Feb 2020 17:18:04 +0700 Subject: [PATCH] Better cache control when not using CDN --- config.sample.js | 9 ++-- controllers/albumsController.js | 14 ++--- lolisafe.js | 91 +++++++++++++++++++++------------ 3 files changed, 66 insertions(+), 48 deletions(-) diff --git a/config.sample.js b/config.sample.js index 141f404..02eecb5 100644 --- a/config.sample.js +++ b/config.sample.js @@ -101,7 +101,8 @@ module.exports = { /* 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, @@ -141,13 +142,13 @@ module.exports = { } }, { - // 4 requests in 30 seconds + // 6 requests in 30 seconds routes: [ '/api/album/zip' ], config: { windowMs: 30 * 1000, - max: 4 + max: 6 } }, { @@ -449,6 +450,8 @@ module.exports = { ADVANCED: Use safe.fiery.me-exclusive cache control. This will only work properly with certain settings in nginx reverse proxy and Cloudflare. Do NOT enable unless you know what you are doing. + true: With CDN (Cloudflare) + 2: When NOT using Cloudflare */ cacheControl: false, diff --git a/controllers/albumsController.js b/controllers/albumsController.js index 66285e0..5c2fb85 100644 --- a/controllers/albumsController.js +++ b/controllers/albumsController.js @@ -352,14 +352,6 @@ self.get = async (req, res, next) => { self.generateZip = async (req, res, next) => { 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 if (identifier === undefined) @@ -398,7 +390,7 @@ self.generateZip = async (req, res, next) => { try { const filePath = path.join(paths.zips, `${identifier}.zip`) await paths.access(filePath) - return download(filePath, `${album.name}.zip`) + return res.download(filePath, `${album.name}.zip`) } catch (error) { // Re-throw error if (error.code !== 'ENOENT') @@ -409,7 +401,7 @@ self.generateZip = async (req, res, next) => { logger.log(`Waiting previous zip task for album: ${identifier}.`) return self.zipEmitters.get(identifier).once('done', (filePath, fileName, json) => { if (filePath && fileName) - download(filePath, fileName) + res.download(filePath, fileName) else if (json) res.json(json) }) @@ -481,7 +473,7 @@ self.generateZip = async (req, res, next) => { const fileName = `${album.name}.zip` self.zipEmitters.get(identifier).emit('done', filePath, fileName) - return download(filePath, fileName) + return res.download(filePath, fileName) } catch (error) { logger.error(error) return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' }) diff --git a/lolisafe.js b/lolisafe.js index 947f6fc..d87eeff 100644 --- a/lolisafe.js +++ b/lolisafe.js @@ -50,52 +50,76 @@ if (Array.isArray(config.rateLimits) && config.rateLimits.length) safe.use(bodyParser.urlencoded({ extended: true })) safe.use(bodyParser.json()) -let setHeaders +let cdnPages +let setHeaders = res => { + res.set('Access-Control-Allow-Origin', '*') +} // Cache control (safe.fiery.me) if (config.cacheControl) { const cacheControls = { - // max-age: 30 days - default: 'public, max-age=2592000, must-revalidate, proxy-revalidate, immutable, stale-while-revalidate=86400, stale-if-error=604800', - // s-max-age: 30 days (only cache in proxy server) - // Obviously we have to purge proxy cache on every update - proxyOnly: 's-max-age=2592000, proxy-revalidate, stale-while-revalidate=86400, stale-if-error=604800', + // max-age: 6 months + static: 'public, max-age=15778800, must-revalidate, proxy-revalidate, immutable, stale-while-revalidate=86400, stale-if-error=604800', + // s-max-age: 6 months (only cache in CDN) + cdn: 's-max-age=15778800, 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' } + // By default soft cache everything safe.use('/', (req, res, next) => { - res.set('Cache-Control', cacheControls.proxyOnly) + res.set('Cache-Control', cacheControls.validate) next() }) - // Do NOT cache these dynamic routes - safe.use(['/a', '/api', '/nojs'], (req, res, next) => { - res.set('Cache-Control', cacheControls.disable) - next() - }) + // If using CDN, cache public pages in CDN + if (config.cacheControl !== 2) { + cdnPages = config.pages.concat(['api/check']) + 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 - safe.use(['/api/check'], (req, res, next) => { - res.set('Cache-Control', cacheControls.proxyOnly) - next() - }) + // If serving uploads with node + if (config.serveFilesWithNode) + safe.use('/', express.static(paths.uploads, { + 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 - safe.use(['/api/album/zip'], (req, res, next) => { - setHeaders(res) - next() - }) - - // For static assets (and uploads if serving with node) + // Function for static assets. + // This requires the assets to use version in their query string, + // as they will be cached by clients for a very long time. setHeaders = res => { 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) - safe.use('/', express.static(paths.uploads, { setHeaders })) - +// Static assets safe.use('/', express.static(paths.public, { setHeaders })) safe.use('/', express.static(paths.dist, { setHeaders })) @@ -142,13 +166,13 @@ safe.use('/api', api) // Error pages 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])) }) safe.use((error, req, res, next) => { 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])) }) @@ -196,12 +220,11 @@ safe.use('/api', api) logger.log(`lolisafe started on port ${config.port}`) // Cache control (safe.fiery.me) - // Also only if explicitly using Cloudflare - if (config.cacheControl) + // Purge Cloudflare cache + if (config.cacheControl === 1) if (config.cloudflare.purgeCache) { logger.log('Cache control enabled, purging Cloudflare\'s cache...') - const routes = config.pages.concat(['api/check']) - const results = await utils.purgeCloudflareCache(routes) + const results = await utils.purgeCloudflareCache(cdnPages) let errored = false let succeeded = 0 for (const result of results) {