Skip to content

Commit

Permalink
cache archived enterproxy proxy responses much longer (github#23456)
Browse files Browse the repository at this point in the history
* cache archived enterproxy proxy responses much longer

* Update middleware/archived-enterprise-versions.js

Co-authored-by: Rachael Sewell <rachmari@github.com>

Co-authored-by: Rachael Sewell <rachmari@github.com>
  • Loading branch information
peterbe and rachmari authored Dec 8, 2021
1 parent ca39429 commit 7b5711e
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 25 deletions.
8 changes: 1 addition & 7 deletions .github/actions-scripts/purge-fastly-edge-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,4 @@

import purgeEdgeCache from '../../script/deployment/purge-edge-cache.js'

try {
await purgeEdgeCache()
} catch (error) {
console.error(`Failed to purge the edge cache: ${error.message}`)
console.error(error)
throw error
}
await purgeEdgeCache()
2 changes: 1 addition & 1 deletion .github/workflows/prod-build-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ jobs:
env:
FASTLY_TOKEN: ${{ secrets.FASTLY_TOKEN }}
FASTLY_SERVICE_ID: ${{ secrets.FASTLY_SERVICE_ID }}
FASTLY_SURROGATE_KEY: 'all-the-things'
FASTLY_SURROGATE_KEY: 'every-deployment'
run: .github/actions-scripts/purge-fastly-edge-cache.js

- name: Send Slack notification if workflow failed
Expand Down
31 changes: 27 additions & 4 deletions middleware/archived-enterprise-versions.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import patterns from '../lib/patterns.js'
import versionSatisfiesRange from '../lib/version-satisfies-range.js'
import isArchivedVersion from '../lib/is-archived-version.js'
import { setFastlySurrogateKey, SURROGATE_ENUMS } from './set-fastly-surrogate-key.js'
import got from 'got'
import { readCompressedJsonFileFallback } from '../lib/read-json-file.js'
import { cacheControlFactory } from './cache-control.js'
Expand Down Expand Up @@ -55,6 +56,25 @@ const archivedFrontmatterFallbacks = readJsonFileLazily(

const cacheControl = cacheControlFactory(60 * 60 * 24 * 365)

// Combine all the things you need to make sure the response is
// aggresively cached.
const cacheAggressively = (res) => {
cacheControl(res)

// This sets a custom Fastly surrogate key so that this response
// won't get updated in every deployment.
// Essentially, this sets a surrogate key such that Fastly
// doesn't do soft-purges on these responses on every
// automated deployment.
setFastlySurrogateKey(res, SURROGATE_ENUMS.MANUAL)

// Because this middleware has (quite possibly) been executed before
// the CSRF middleware, that would have set a cookie. Remove that.
// The reason for removing the 'Set-Cookie' header is because
// otherwise Fastly won't cache it.
res.removeHeader('set-cookie')
}

// The way `got` does retries:
//
// sleep = 1000 * Math.pow(2, retry - 1) + Math.random() * 100
Expand Down Expand Up @@ -119,7 +139,7 @@ export default async function archivedEnterpriseVersions(req, res, next) {
// and memoized so calling it is cheap.
const redirect = archivedRedirects()[req.path]
if (redirect && redirect !== req.path) {
cacheControl(res)
cacheAggressively(res)
return res.redirect(redirectCode, redirect)
}
}
Expand All @@ -138,7 +158,7 @@ export default async function archivedEnterpriseVersions(req, res, next) {
// make redirects found via redirects.json redirect with a 301
if (redirectJson[req.path]) {
res.set('x-robots-tag', 'noindex')
cacheControl(res)
cacheAggressively(res)
return res.redirect(redirectCode, redirectJson[req.path])
}
}
Expand All @@ -160,11 +180,14 @@ export default async function archivedEnterpriseVersions(req, res, next) {
// make stubbed redirect files (which exist in versions <2.13) redirect with a 301
const staticRedirect = r.body.match(patterns.staticRedirect)
if (staticRedirect) {
cacheControl(res)
cacheAggressively(res)
return res.redirect(redirectCode, staticRedirect[1])
}

res.set('content-type', r.headers['content-type'])

cacheAggressively(res)

return res.send(r.body)
}

Expand All @@ -181,7 +204,7 @@ export default async function archivedEnterpriseVersions(req, res, next) {
`fallback:${fallbackRedirect}`,
])()
if (r.statusCode === 200) {
cacheControl(res)
cacheAggressively(res)
return res.redirect(redirectCode, fallbackRedirect)
}
}
Expand Down
4 changes: 2 additions & 2 deletions middleware/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import csrf from './csrf.js'
import handleCsrfErrors from './handle-csrf-errors.js'
import compression from 'compression'
import disableCachingOnSafari from './disable-caching-on-safari.js'
import setFastlySurrogateKey from './set-fastly-surrogate-key.js'
import setDefaultFastlySurrogateKey from './set-fastly-surrogate-key.js'
import setFastlyCacheHeaders from './set-fastly-cache-headers.js'
import catchBadAcceptLanguage from './catch-bad-accept-language.js'
import reqUtils from './req-utils.js'
Expand Down Expand Up @@ -84,7 +84,7 @@ export default function (app) {
// Must appear before static assets and all other requests
// otherwise we won't be able to benefit from that functionality
// for static assets as well.
app.use(setFastlySurrogateKey)
app.use(setDefaultFastlySurrogateKey)

// Must come before `csrf` otherwise you get a Set-Cookie on successful
// asset requests. And it can come before `rateLimit` because if it's a
Expand Down
35 changes: 26 additions & 9 deletions middleware/set-fastly-surrogate-key.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
export default function setFastlySurrogateKey(req, res, next) {
// Fastly provides a Soft Purge feature that allows you to mark content as outdated (stale) instead of permanently
// purging and thereby deleting it from Fastly's caches. Objects invalidated with Soft Purge will be treated as
// outdated (stale) while Fastly fetches a new version from origin.
//
// Use of a surrogate key is required for soft purging
// https://docs.fastly.com/en/guides/soft-purges
// https://docs.fastly.com/en/guides/getting-started-with-surrogate-keys
res.set('surrogate-key', 'all-the-things')
// Fastly provides a Soft Purge feature that allows you to mark content as outdated (stale) instead of permanently
// purging and thereby deleting it from Fastly's caches. Objects invalidated with Soft Purge will be treated as
// outdated (stale) while Fastly fetches a new version from origin.
//
// Use of a surrogate key is required for soft purging
// https://docs.fastly.com/en/guides/soft-purges
// https://docs.fastly.com/en/guides/getting-started-with-surrogate-keys

// What the header needs to be called for Fastly to recognize it.
const KEY = 'surrogate-key'

export const SURROGATE_ENUMS = {
DEFAULT: 'every-deployment',
MANUAL: 'manual-purge',
}

export function setFastlySurrogateKey(res, enumKey) {
if (!Object.values(SURROGATE_ENUMS).includes(enumKey)) {
throw new Error(
`Unrecognizes surrogate enumKey. ${enumKey} is not one of ${Object.values(SURROGATE_ENUMS)}`
)
}
res.set(KEY, enumKey)
}

export default function setDefaultFastlySurrogateKey(req, res, next) {
res.set(KEY, SURROGATE_ENUMS.DEFAULT)
return next()
}
3 changes: 2 additions & 1 deletion tests/rendering/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { describeViaActionsOnly } from '../helpers/conditional-runs.js'
import { loadPages } from '../../lib/page-data.js'
import CspParse from 'csp-parse'
import { productMap } from '../../lib/all-products.js'
import { SURROGATE_ENUMS } from '../../middleware/set-fastly-surrogate-key.js'
import { jest } from '@jest/globals'

const AZURE_STORAGE_URL = 'githubdocs.azureedge.net'
Expand Down Expand Up @@ -137,7 +138,7 @@ describe('server', () => {
const res = await get('/en')
expect(res.headers['cache-control']).toBe('private, no-store')
expect(res.headers['surrogate-control']).toBe('private, no-store')
expect(res.headers['surrogate-key']).toBe('all-the-things')
expect(res.headers['surrogate-key']).toBe(SURROGATE_ENUMS.DEFAULT)
})

test('does not render duplicate <html> or <body> tags', async () => {
Expand Down
5 changes: 4 additions & 1 deletion tests/routing/deprecated-enterprise-versions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import createApp from '../../lib/app.js'
import enterpriseServerReleases from '../../lib/enterprise-server-releases.js'
import { get, getDOM } from '../helpers/supertest.js'
import { SURROGATE_ENUMS } from '../../middleware/set-fastly-surrogate-key.js'
import supertest from 'supertest'
import { jest } from '@jest/globals'

Expand Down Expand Up @@ -54,10 +55,12 @@ describe('enterprise deprecation', () => {
expect($('h1').text()).toBe('About branches')
})

test('sets the expected x-robots-tag header for deprecated Enterprise pages', async () => {
test('sets the expected headers for deprecated Enterprise pages', async () => {
const res = await get('/en/enterprise/2.13/user/articles/about-branches')
expect(res.statusCode).toBe(200)
expect(res.get('x-robots-tag')).toBe('noindex')
expect(res.get('surrogate-key')).toBe(SURROGATE_ENUMS.MANUAL)
expect(res.get('set-cookie')).toBeUndefined()
})

test('handles requests for deprecated Enterprise pages ( <2.13 )', async () => {
Expand Down

0 comments on commit 7b5711e

Please sign in to comment.