14
middleware/fastly-behavior.js
Normal file
14
middleware/fastly-behavior.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// This middleware allows the client to cause the server-side processing to fail with a specific error.
|
||||
// It is used for testing error handling with Fastly. It should only be enabled in non-production environments!
|
||||
//
|
||||
// NOTE: This middleware is intended to be removed once testing is complete!
|
||||
//
|
||||
export default function fastlyBehavior(req, res, next) {
|
||||
if ((req.method === 'GET' || req.method === 'HEAD') && req.get('X-CacheTest-Error')) {
|
||||
const error = parseInt(req.get('X-CacheTest-Error'))
|
||||
res.status(error).send(`SIMULATED ERROR ${error}`)
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
@@ -7,21 +7,36 @@
|
||||
//
|
||||
// NOTE: This middleware is intended to be removed once testing is complete!
|
||||
//
|
||||
export default function fastlyCacheTest(req, res, next) {
|
||||
import express from 'express'
|
||||
import crypto from 'crypto'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
router.get('/*', function (req, res) {
|
||||
// If X-CacheTest-Error is set, simulate the site being down (regardless of URL)
|
||||
if (req.get('X-CacheTest-Error')) {
|
||||
res.status(parseInt(req.get('X-CacheTest-Error'))).end()
|
||||
return
|
||||
}
|
||||
|
||||
const cacheControlModeParam = req.get('X-CacheTest-CCMode') ?? 'none'
|
||||
const staleIfErrorParam = req.get('X-CacheTest-StaleIfError') ?? '300'
|
||||
const staleWhileRevalidateParam = req.get('X-CacheTest-StaleWhileRevalidate') ?? '60'
|
||||
const maxAgeParam = req.get('X-CacheTest-MaxAge') ?? '300'
|
||||
|
||||
const path = req.params[0]
|
||||
|
||||
const content = `<html>
|
||||
<body>
|
||||
<p>Timestamp: ${new Date()}</p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
let status = 200
|
||||
const surrogateControlValues = []
|
||||
|
||||
if (path.includes('max-age')) surrogateControlValues.push(`max-age=${maxAgeParam}`)
|
||||
|
||||
if (path.includes('must-revalidate')) surrogateControlValues.push('must-revalidate')
|
||||
if (path.includes('no-cache')) surrogateControlValues.push('no-cache')
|
||||
if (path.includes('no-store')) surrogateControlValues.push('no-store')
|
||||
@@ -38,6 +53,12 @@ export default function fastlyCacheTest(req, res, next) {
|
||||
res.removeHeader('Set-Cookie')
|
||||
}
|
||||
|
||||
if (path.includes('etag')) {
|
||||
res.set({
|
||||
ETag: `"${crypto.createHash('md5').update(content).digest('hex')}"`,
|
||||
})
|
||||
}
|
||||
|
||||
if (path.includes('error500')) {
|
||||
status = 500
|
||||
} else if (path.includes('error502')) {
|
||||
@@ -49,16 +70,31 @@ export default function fastlyCacheTest(req, res, next) {
|
||||
}
|
||||
|
||||
if (surrogateControlValues.length > 0) {
|
||||
res.set({
|
||||
'surrogate-control': surrogateControlValues.join(', '),
|
||||
})
|
||||
switch (cacheControlModeParam) {
|
||||
case 'none':
|
||||
res.set({
|
||||
'surrogate-control': surrogateControlValues.join(', '),
|
||||
})
|
||||
break
|
||||
|
||||
case 'both':
|
||||
res.set({
|
||||
'surrogate-control': surrogateControlValues.join(', '),
|
||||
'cache-control': surrogateControlValues.join(', ').replace('max-age', 's-maxage'),
|
||||
})
|
||||
break
|
||||
|
||||
case 'only':
|
||||
res.set({
|
||||
'cache-control': surrogateControlValues.join(', ').replace('max-age', 's-maxage'),
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
res.status(status)
|
||||
res.send(`<html>
|
||||
<body>
|
||||
<p>Timestamp: ${new Date()}</p>
|
||||
</body>
|
||||
</html>`)
|
||||
res.send(content)
|
||||
res.end()
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -65,11 +65,14 @@ import fastHead from './fast-head.js'
|
||||
import fastlyCacheTest from './fastly-cache-test.js'
|
||||
import fastRootRedirect from './fast-root-redirect.js'
|
||||
import trailingSlashes from './trailing-slashes.js'
|
||||
import fastlyBehavior from './fastly-behavior.js'
|
||||
|
||||
const { DEPLOYMENT_ENV, NODE_ENV } = process.env
|
||||
const isAzureDeployment = DEPLOYMENT_ENV === 'azure'
|
||||
const isTest = NODE_ENV === 'test' || process.env.GITHUB_ACTIONS === 'true'
|
||||
|
||||
const ENABLE_FASTLY_TESTING = JSON.parse(process.env.ENABLE_FASTLY_TESTING || 'false')
|
||||
|
||||
// Catch unhandled promise rejections and passing them to Express's error handler
|
||||
// https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
|
||||
const asyncMiddleware = (fn) => (req, res, next) => {
|
||||
@@ -218,6 +221,11 @@ export default function (app) {
|
||||
app.use(csp) // Must come after helmet
|
||||
app.use(cookieParser) // Must come before csrf
|
||||
app.use(express.json()) // Must come before csrf
|
||||
|
||||
if (ENABLE_FASTLY_TESTING) {
|
||||
app.use(fastlyBehavior) // FOR TESTING. Must come before csrf
|
||||
}
|
||||
|
||||
app.use(csrf)
|
||||
app.use(handleCsrfErrors) // Must come before regular handle-errors
|
||||
|
||||
@@ -296,10 +304,12 @@ export default function (app) {
|
||||
app.use(asyncMiddleware(instrument(featuredLinks, './featured-links')))
|
||||
app.use(asyncMiddleware(instrument(learningTrack, './learning-track')))
|
||||
|
||||
// The fastlyCacheTest middleware is intended to be used with Fastly to test caching behavior.
|
||||
// This middleware will intercept ALL requests routed to it, so be careful if you need to
|
||||
// make any changes to the following line:
|
||||
app.use('/fastly-cache-test/*', fastlyCacheTest)
|
||||
if (ENABLE_FASTLY_TESTING) {
|
||||
// The fastlyCacheTest middleware is intended to be used with Fastly to test caching behavior.
|
||||
// This middleware will intercept ALL requests routed to it, so be careful if you need to
|
||||
// make any changes to the following line:
|
||||
app.use('/fastly-cache-test', fastlyCacheTest)
|
||||
}
|
||||
|
||||
// *** Headers for pages only ***
|
||||
app.use(setFastlyCacheHeaders)
|
||||
|
||||
@@ -42,4 +42,10 @@ module.exports = {
|
||||
|
||||
// https://nextjs.org/docs/api-reference/next.config.js/compression
|
||||
compress: false,
|
||||
|
||||
// ETags break stale content serving from the CDN. When a response has
|
||||
// an ETag, the CDN attempts to revalidate the content in the background.
|
||||
// This causes problems with serving stale content, since upon revalidating
|
||||
// the CDN marks the cached content as "fresh".
|
||||
generateEtags: false,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user