Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion app/utils/getCacheAnalysis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('getCacheAnalysis', () => {
expect(result).toHaveProperty('servedBy')
expect(result).toHaveProperty('cacheStatus')
expect(result).toHaveProperty('cacheControl')
expect(result.servedBy.source).toBe(ServedBySource.CDN)
expect(result.servedBy.source).toBe(ServedBySource.CdnEdge)
})

it('integrates Cache-Status parsing correctly', () => {
Expand Down Expand Up @@ -66,6 +66,23 @@ describe('getCacheAnalysis', () => {

expect(() => getCacheAnalysis(headers, now)).toThrow('Could not determine who served the request')
})

it('returns CdnOrigin when Netlify Edge has a cache miss', () => {
const headers = {
'cache-status': '"Netlify Edge"; fwd=miss',
'debug-x-bb-host-id': 'cdn-glo-aws-cmh-57',
}
const now = Date.now()

const result = getCacheAnalysis(headers, now)

expect(result.servedBy.source).toBe(ServedBySource.CdnOrigin)
expect(result.servedBy.cdnNodes).toBe('cdn-glo-aws-cmh-57')
expect(result.cacheStatus).toHaveLength(1)
expect(result.cacheStatus[0]?.cacheName).toBe('Netlify Edge')
expect(result.cacheStatus[0]?.parameters.hit).toBe(false)
expect(result.cacheStatus[0]?.parameters.fwd).toBe('miss')
})
})

describe('parseCacheStatus', () => {
Expand Down
104 changes: 96 additions & 8 deletions app/utils/getServedBy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { describe, it, expect } from 'vitest'
import { getServedBy, ServedBySource, type ParsedCacheStatusEntry } from './getServedBy'

describe('getServedBy', () => {
it('returns CDN when Netlify Edge cache has a hit', () => {
it('returns CdnEdge when Netlify Edge cache has a hit', () => {
const headers = new Headers({
'Debug-X-BB-Host-Id': 'node1.example.com',
})
Expand All @@ -20,11 +20,11 @@ describe('getServedBy', () => {

const result = getServedBy(headers, cacheStatus)

expect(result.source).toBe(ServedBySource.CDN)
expect(result.source).toBe(ServedBySource.CdnEdge)
expect(result.cdnNodes).toBe('node1.example.com')
})

it('prioritizes CDN hit over durable cache hit when both are present', () => {
it('prioritizes CdnEdge hit over durable cache hit when both are present', () => {
const headers = new Headers({
'Debug-X-BB-Host-Id': 'node1.example.com',
})
Expand All @@ -49,14 +49,23 @@ describe('getServedBy', () => {

const result = getServedBy(headers, cacheStatus)

expect(result.source).toBe(ServedBySource.CDN)
expect(result.source).toBe(ServedBySource.CdnEdge)
})

it('returns DurableCache when Netlify Durable cache has a hit', () => {
const headers = new Headers({
'Debug-X-BB-Host-Id': 'node1.example.com',
})
const cacheStatus: ParsedCacheStatusEntry[] = [
{
cacheName: 'Netlify Edge',
parameters: {
hit: false,
fwd: 'miss',
stored: false,
collapsed: false,
},
},
{
cacheName: 'Netlify Durable',
parameters: {
Expand All @@ -77,7 +86,26 @@ describe('getServedBy', () => {
'Debug-X-NF-Function-Type': 'edge',
'Debug-X-BB-Host-Id': 'node1.example.com',
})
const cacheStatus: ParsedCacheStatusEntry[] = []
const cacheStatus: ParsedCacheStatusEntry[] = [
{
cacheName: 'Netlify Edge',
parameters: {
hit: false,
fwd: 'miss',
stored: false,
collapsed: false,
},
},
{
cacheName: 'Netlify Durable',
parameters: {
hit: false,
fwd: 'miss',
stored: false,
collapsed: false,
},
},
]

const result = getServedBy(headers, cacheStatus)

Expand All @@ -90,7 +118,26 @@ describe('getServedBy', () => {
'Debug-X-NF-Edge-Functions': 'middleware',
'Debug-X-BB-Host-Id': 'node1.example.com',
})
const cacheStatus: ParsedCacheStatusEntry[] = []
const cacheStatus: ParsedCacheStatusEntry[] = [
{
cacheName: 'Netlify Edge',
parameters: {
hit: false,
fwd: 'miss',
stored: false,
collapsed: false,
},
},
{
cacheName: 'Netlify Durable',
parameters: {
hit: false,
fwd: 'miss',
stored: false,
collapsed: false,
},
},
]

const result = getServedBy(headers, cacheStatus)

Expand All @@ -102,7 +149,26 @@ describe('getServedBy', () => {
'Debug-X-NF-Edge-Functions': 'middleware',
'Debug-X-BB-Host-Id': 'node1.example.com',
})
const cacheStatus: ParsedCacheStatusEntry[] = []
const cacheStatus: ParsedCacheStatusEntry[] = [
{
cacheName: 'Netlify Edge',
parameters: {
hit: false,
fwd: 'miss',
stored: false,
collapsed: false,
},
},
{
cacheName: 'Netlify Durable',
parameters: {
hit: false,
fwd: 'miss',
stored: false,
collapsed: false,
},
},
]

const result = getServedBy(headers, cacheStatus)

Expand Down Expand Up @@ -156,7 +222,29 @@ describe('getServedBy', () => {
)
})

it('ignores cache entries without hits', () => {
it('returns CdnOrigin when Netlify Edge cache has a miss', () => {
const headers = new Headers({
'Debug-X-BB-Host-Id': 'node1.example.com',
})
const cacheStatus: ParsedCacheStatusEntry[] = [
{
cacheName: 'Netlify Edge',
parameters: {
hit: false,
fwd: 'miss',
stored: false,
collapsed: false,
},
},
]

const result = getServedBy(headers, cacheStatus)

expect(result.source).toBe(ServedBySource.CdnOrigin)
expect(result.cdnNodes).toBe('node1.example.com')
})

it('ignores cache entries without hits and picks first with hit', () => {
const headers = new Headers({
'Debug-X-BB-Host-Id': 'node1.example.com',
})
Expand Down
19 changes: 17 additions & 2 deletions app/utils/getServedBy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export enum ServedBySource {
CDN = 'CDN',
CdnEdge = 'CDN edge',
CdnOrigin = 'CDN origin',
DurableCache = 'Durable Cache',
Function = 'Function',
EdgeFunction = 'Edge Function',
Expand Down Expand Up @@ -40,13 +41,15 @@ const getServedBySource = (
// So, the first cache hit (starting from the user) is the one that served the request.
// But we don't quite want to return exactly the same concept of "caches" as in `Cache-Status`, so
// we need a bit of extra logic to map to other sources.

// First, check for cache hits
for (const {
cacheName,
parameters: { hit },
} of cacheStatus) {
if (!hit) continue

if (cacheName === 'Netlify Edge') return ServedBySource.CDN
if (cacheName === 'Netlify Edge') return ServedBySource.CdnEdge
if (cacheName === 'Netlify Durable') return ServedBySource.DurableCache
}

Expand All @@ -58,6 +61,18 @@ const getServedBySource = (
if (cacheHeaders.has('Debug-X-NF-Edge-Functions'))
return ServedBySource.EdgeFunction

// Check for the specific case of ONLY a Netlify Edge miss with no other cache entries - this handles
// the weird Netlify Cache-Status behavior where a miss on the CDN edge (with no other caches consulted)
// means the request was forwarded directly to the CDN origin. This can only be detected when there's
// a single cache status entry for Netlify Edge that is a miss, with no entries at all for subsequent caches.
// If there were other cache entries (e.g., Netlify Durable), those caches were consulted, and we'd be in
// a different scenario where it's unclear whether the CDN origin or actual origin served the request.
if (cacheStatus.length === 1
&& cacheStatus[0]?.cacheName === 'Netlify Edge'
&& !cacheStatus[0]?.parameters.hit) {
return ServedBySource.CdnOrigin
}

throw new Error(
`Could not determine who served the request. Cache status: ${cacheStatus}`,
)
Expand Down