Skip to content

Commit 2c38134

Browse files
authored
Merge pull request github#15301 from github/repo-sync
repo sync
2 parents 8192db4 + b6a1b69 commit 2c38134

File tree

2 files changed

+39
-6
lines changed

2 files changed

+39
-6
lines changed

middleware/handle-errors.js

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import FailBot from '../lib/failbot.js'
22
import { nextApp } from './next.js'
3+
import { setFastlySurrogateKey, SURROGATE_ENUMS } from './set-fastly-surrogate-key.js'
4+
import { cacheControlFactory } from './cache-control.js'
5+
6+
const cacheControl = cacheControlFactory(60) // 1 minute
37

48
function shouldLogException(error) {
59
const IGNORED_ERRORS = [
@@ -32,10 +36,34 @@ export default async function handleError(error, req, res, next) {
3236
// anywhere. So this is why we log it additionally.
3337
// Note, not using console.error() because it's arguably handled.
3438
// Some tests might actually expect a 500 error.
35-
if (
36-
process.env.NODE_ENV === 'test' &&
37-
!(req.path.startsWith('/assets') || req.path.startsWith('/_next/static'))
38-
) {
39+
40+
if (req.path.startsWith('/assets') || req.path.startsWith('/_next/static')) {
41+
// By default, Fastly will cache 404 responses unless otherwise
42+
// told not to.
43+
// See https://docs.fastly.com/en/guides/how-caching-and-cdns-work#http-status-codes-cached-by-default
44+
// Most of the time, that's a good thing! Especially, if bombarded
45+
// for some static asset that we don't have.
46+
// E.g. `ab -n 10000 https://docs.github.com/assets/doesnotexist.png`
47+
// But due to potential timing issue related to how the servers start,
48+
// what might happen is that a new insteance comes up that
49+
// contains `<script src="/_next/static/foo.1234.css">` in the HTML.
50+
// The browser then proceeds to request /_next/static/foo.1234.css
51+
// but this time it could be unluckily routed to a different instance
52+
// that hasn't yet been upgraded, so they get a 404. And the CDN will
53+
// notice this and cache it.
54+
// Setting a tiny cache gets us a good compromise. It protects us
55+
// against most stamping herds on 404s (thank you CDN!) but it also
56+
// clears itself if you get that unlucky timing issue with rolling
57+
// instances in a new deployment.
58+
// For more background see issue 1553.
59+
cacheControl(res)
60+
// Undo the cookie setting that CSRF sets.
61+
res.removeHeader('set-cookie')
62+
// Makes sure the surrogate key is NOT the manual one if it failed.
63+
// This basically unsets what was assumed in the beginning of
64+
// loading all the middlewares.
65+
setFastlySurrogateKey(res, SURROGATE_ENUMS.DEFAULT)
66+
} else if (process.env.NODE_ENV === 'test') {
3967
console.warn('An error occurrred in some middleware handler', error)
4068
}
4169

tests/rendering/static-assets.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,19 @@ function getNextStaticAsset(directory) {
1313
return path.join(root, files[0])
1414
}
1515

16-
function checkCachingHeaders(res, defaultSurrogateKey = false) {
16+
function checkCachingHeaders(res, defaultSurrogateKey = false, minMaxAge = 60 * 60) {
1717
expect(res.headers['set-cookie']).toBeUndefined()
1818
expect(res.headers['cache-control']).toContain('public')
1919
const maxAgeSeconds = parseInt(res.header['cache-control'].match(/max-age=(\d+)/)[1], 10)
2020
// Let's not be too specific in the tests, just as long as it's testing
2121
// that it's a reasonably large number of seconds.
22-
expect(maxAgeSeconds).toBeGreaterThanOrEqual(60 * 60)
22+
expect(maxAgeSeconds).toBeGreaterThanOrEqual(minMaxAge)
2323
// Because it doesn't have have a unique URL
2424
expect(res.headers['surrogate-key']).toBe(
2525
defaultSurrogateKey ? SURROGATE_ENUMS.DEFAULT : SURROGATE_ENUMS.MANUAL
2626
)
2727
}
28+
2829
describe('static assets', () => {
2930
it('should serve /assets/cb-* with optimal headers', async () => {
3031
const res = await get('/assets/cb-1234/images/site/logo.png')
@@ -52,15 +53,19 @@ describe('static assets', () => {
5253
const res = await get('/assets/cb-1234/never/heard/of.png')
5354
expect(res.statusCode).toBe(404)
5455
expect(res.header['content-type']).toContain('text/plain')
56+
// Only a tiny amount of Cache-Control on these
57+
checkCachingHeaders(res, true, 60)
5558
})
5659
it('should 404 on /assets/ with plain text', async () => {
5760
const res = await get('/assets/never/heard/of.png')
5861
expect(res.statusCode).toBe(404)
5962
expect(res.header['content-type']).toContain('text/plain')
63+
checkCachingHeaders(res, true, 60)
6064
})
6165
it('should 404 on /_next/static/ with plain text', async () => {
6266
const res = await get('/_next/static/never/heard/of.css')
6367
expect(res.statusCode).toBe(404)
6468
expect(res.header['content-type']).toContain('text/plain')
69+
checkCachingHeaders(res, true, 60)
6570
})
6671
})

0 commit comments

Comments
 (0)