Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Chore] Enable caching #11

Merged
merged 28 commits into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b13ddc7
[WIP] Add caching to the cloudflare router
samkahchiin Jun 1, 2022
c23866a
[WIP] Declare caches type
samkahchiin Jun 1, 2022
78157cc
[Hotfix] Fix cache types
samkahchiin Aug 29, 2022
eaf0a01
[Test] Mock cache api in test
samkahchiin Aug 29, 2022
36f028c
[Hotfix] Update max-age for caching
samkahchiin Aug 29, 2022
6ae4cd0
Update src/cloudflare-router.ts
samkahchiin Sep 1, 2022
a03d5f4
WIP: debugging
swiknaba Sep 1, 2022
7ab9c39
[WIP] Add headers to the headers
samkahchiin Sep 1, 2022
06ce0b7
version bump
swiknaba Sep 1, 2022
e6979ec
[Hotfix] Put body and status into the headers
samkahchiin Sep 1, 2022
86d97a1
[Finishes] Add request url to the headers
samkahchiin Sep 1, 2022
b959620
[WIP] Working script
samkahchiin Sep 1, 2022
89f0902
[WIP] Remove redundant codes
samkahchiin Sep 1, 2022
60fa2d4
[Hotfix] Remove cache mock
samkahchiin Sep 1, 2022
50ecebd
[Hotfix] Use request url as cacheKey
samkahchiin Sep 1, 2022
9750df4
[Hotfix] Set to 0.2.15
samkahchiin Sep 1, 2022
ce593b6
[WIP] CacheEverything
samkahchiin Sep 1, 2022
bd4a90e
[Chore] Update notes
samkahchiin Sep 5, 2022
66ddcf9
[Chore] Console log the original Url
samkahchiin Sep 5, 2022
b2a270a
[Hotfix] Fix cloudflare script
samkahchiin Sep 5, 2022
641cb8e
[Hotfix] Change to edceCacheTtl
samkahchiin Sep 5, 2022
b43b5f3
[Hotfix] Fix test
samkahchiin Sep 5, 2022
5a73a4c
[Hotfix] Add edge cache ttl as headers
samkahchiin Sep 5, 2022
4063492
[Refactor] Refactor the codes
samkahchiin Sep 5, 2022
7e890f7
[Chore] Bump version
samkahchiin Sep 5, 2022
441d1a6
Update src/utils/handle-request.ts
samkahchiin Sep 6, 2022
41d89e7
Update package.json
samkahchiin Sep 6, 2022
8e99384
[Hotfix] Remove console.log
samkahchiin Sep 6, 2022
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ startWorker({
routes: {
'example.com': 's3://eu-central-1.assets.example.com',
},
edgeCacheTtl: 360 // seconds, Edge Cache TTL (Time to Live) specifies how long to cache a resource in the Cloudflare edge network
})
```

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cloudflare-router",
"version": "0.1.0",
"version": "0.2.0",
"main": "dist/index.js",
"scripts": {
"build": "rm -rf dist && tsc",
Expand Down
10 changes: 6 additions & 4 deletions src/cloudflare-router.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Config, DEFAULT_CONFIG } from './config'
import { Config } from './config'
import handleRequest from './utils/handle-request'
import normalizeRequest from './utils/normalize-request'
import { withAuth } from './utils/with-auth'

export const startWorker = (config: Config) => {
addEventListener('fetch', (event: any) => {
withAuth(event, config, () => {
const request = normalizeRequest(event.request, config.routes)
return fetch(request)
withAuth(event, config, async () => {
const { request, cache }= normalizeRequest(event.request, config.routes)
const edgeCacheTtl = cache ? config.edgeCacheTtl : 0
return handleRequest(request, edgeCacheTtl)
})
})
}
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ export interface Deployment {
export interface Config {
deployments: Deployment[]
routes: Routes
edgeCacheTtl: number
}

export const DEFAULT_CONFIG: Config = {
deployments: [],
routes: {},
edgeCacheTtl: 86400
}
1 change: 0 additions & 1 deletion src/utils/deployment-for-request.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Config, Deployment } from '../config'

export const deploymentForRequest = (request: Request, config: Config): Deployment | undefined => {
const url = new URL(request.url)
return config.deployments.find(deployment => {
return deployment.routes.find(route => {
const sanitizedRoute = route.replace(/[^a-zA-Z0-9*\.\-\/]/g, '') // We only really want to allow the pattern *.example.com/*
Expand Down
29 changes: 29 additions & 0 deletions src/utils/handle-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export default async function handleRequest(request: Request, edgeCacheTtl: number) {
const cfOptions = edgeCacheTtl > 0 ? {
// Requests proxied through Cloudflare are cached even without Workers according to a zone’s default
// or configured behavior. Setting Cloudflare cache rules to further customised the behavior
cf: {
// NOTE: cf is Enterprise only feature
// This is equivalent to setting two Page Rules:
// - Edge Cache TTL and
// - Cache Level (to Cache Everything).
// Edge Cache TTL (Time to Live) specifies how long to cache a resource in the Cloudflare edge network
// The value must be zero or a positive number.
// A value of 0 indicates that the cache asset expires immediately.
// This option applies to GET and HEAD request methods only.
cacheTtlByStatus: { '200-299': edgeCacheTtl, '404': 1, '500-599': 0 },
// The Cloudflare CDN does not cache HTML by default without setting cacheEverything to true.
cacheEverything: true,
cacheKey: request.url
}
} : {}

//@ts-ignore
let response = await fetch(request, cfOptions);

// Reconstruct the Response object to make its headers mutable.
response = new Response(response.body, response);
response.headers.set('edge-cache-ttl', `${edgeCacheTtl}`)
return response;
}

7 changes: 4 additions & 3 deletions src/utils/normalize-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const hasMediaFileExtension = (path: string): boolean => {
return ext ? MEDIA_FILE_EXTENSIONS.includes(ext) : false
}

export default function normalizeRequest(request: Request, routes: Config['routes']): Request {
export default function normalizeRequest(request: Request, routes: Config['routes']): { request: Request, cache: boolean } {
const originalUrl = request.url
const originalUrlWithoutScheme = originalUrl.replace(/^https?:\/\//, '')
const path = originalUrlWithoutScheme.replace(/^.*?\//gi, '')
Expand Down Expand Up @@ -38,9 +38,10 @@ export default function normalizeRequest(request: Request, routes: Config['route
if (url.indexOf('https://') !== 0) {
url = 'https://' + url
}
return new Request(url)
// Make sure we only cache requests from the stated routes
return { request: new Request(url), cache: true }
}
}

return request
return { request, cache: false }
}
1 change: 1 addition & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Config } from '../src/config'
const TEST_CONFIG: Config = {
deployments: [],
routes: {},
edgeCacheTtl: 360
}

// Figure out how to mock the request
Expand Down
3 changes: 3 additions & 0 deletions test/utils/deployment-for-request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ test('it finds a deployment for matching request', () => {
MOCK_DEPLOYMENT_1,
],
routes: {},
edgeCacheTtl: 360
}

const deployment = deploymentForRequest(request, config)
Expand All @@ -36,6 +37,7 @@ test('it finds a deployment for matching subdomain', () => {
MOCK_DEPLOYMENT_1,
],
routes: {},
edgeCacheTtl: 360
}

const deployment = deploymentForRequest(request, config)
Expand All @@ -49,6 +51,7 @@ test('it returns undefined when there is no matching request', () => {
MOCK_DEPLOYMENT_1,
],
routes: {},
edgeCacheTtl: 360
}

const deployment = deploymentForRequest(request, config)
Expand Down
55 changes: 33 additions & 22 deletions test/utils/normalize-request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,56 +10,67 @@ const TEST_ROUTES = {
}

test('returns the original input if no matching routes', () => {
const actual = normalizeRequest(new Request('https://example.com/'), TEST_ROUTES)
expect(actual.url).toEqual('https://example.com/')
const { request, cache } = normalizeRequest(new Request('https://example.com/'), TEST_ROUTES)
expect(request.url).toEqual('https://example.com/')
expect(cache).toEqual(false)
})

test('maps root js file to s3 bucket subpath', () => {
const actual = normalizeRequest(new Request('https://admin.example.com/some/file.js'), TEST_ROUTES)
expect(actual.url).toEqual('https://s3.eu-central-1.amazonaws.com/assets.example.com/admin/some/file.js')
const { request, cache } = normalizeRequest(new Request('https://admin.example.com/some/file.js'), TEST_ROUTES)
expect(request.url).toEqual('https://s3.eu-central-1.amazonaws.com/assets.example.com/admin/some/file.js')
expect(cache).toEqual(true)
})

test('maps root path to s3 bucket subpath', () => {
const actual = normalizeRequest(new Request('https://admin.example.com/'), TEST_ROUTES)
expect(actual.url).toEqual('https://s3.eu-central-1.amazonaws.com/assets.example.com/admin/')
const { request, cache } = normalizeRequest(new Request('https://admin.example.com/'), TEST_ROUTES)
expect(request.url).toEqual('https://s3.eu-central-1.amazonaws.com/assets.example.com/admin/')
expect(cache).toEqual(true)
})

test('maps subpath js file to s3 bucket subpath', () => {
const actual = normalizeRequest(new Request('https://example.com/admin/some/file.js'), TEST_ROUTES)
expect(actual.url).toEqual('https://s3.eu-central-1.amazonaws.com/assets.example.com/admin/some/file.js')
const { request, cache } = normalizeRequest(new Request('https://example.com/admin/some/file.js'), TEST_ROUTES)
expect(request.url).toEqual('https://s3.eu-central-1.amazonaws.com/assets.example.com/admin/some/file.js')
expect(cache).toEqual(true)
})

test('maps js to s3 bucket root', () => {
const actual = normalizeRequest(new Request('https://cdn.example.com/some/file.js'), TEST_ROUTES)
expect(actual.url).toEqual('https://s3.eu-central-1.amazonaws.com/cdn.example.com/some/file.js')
const { request, cache } = normalizeRequest(new Request('https://cdn.example.com/some/file.js'), TEST_ROUTES)
expect(request.url).toEqual('https://s3.eu-central-1.amazonaws.com/cdn.example.com/some/file.js')
expect(cache).toEqual(true)
})

test('maps SPA root path to s3 bucket index', () => {
const actual = normalizeRequest(new Request('https://dashboard.example.com/'), TEST_ROUTES)
expect(actual.url).toEqual('https://s3.eu-central-1.amazonaws.com/assets.example.com/dashboard/index.html')
const { request, cache } = normalizeRequest(new Request('https://dashboard.example.com/'), TEST_ROUTES)
expect(request.url).toEqual('https://s3.eu-central-1.amazonaws.com/assets.example.com/dashboard/index.html')
expect(cache).toEqual(true)
})

test('maps SPA sub path to s3 bucket index', () => {
const actual = normalizeRequest(new Request('https://dashboard.example.com/users/'), TEST_ROUTES)
expect(actual.url).toEqual('https://s3.eu-central-1.amazonaws.com/assets.example.com/dashboard/index.html')
const { request, cache } = normalizeRequest(new Request('https://dashboard.example.com/users/'), TEST_ROUTES)
expect(request.url).toEqual('https://s3.eu-central-1.amazonaws.com/assets.example.com/dashboard/index.html')
expect(cache).toEqual(true)
})

test('maps SPA JS FILE to s3 bucket location', () => {
const actual = normalizeRequest(new Request('https://dashboard.example.com/some/file.js'), TEST_ROUTES)
expect(actual.url).toEqual('https://s3.eu-central-1.amazonaws.com/assets.example.com/dashboard/some/file.js')
const { request, cache } = normalizeRequest(new Request('https://dashboard.example.com/some/file.js'), TEST_ROUTES)
expect(request.url).toEqual('https://s3.eu-central-1.amazonaws.com/assets.example.com/dashboard/some/file.js')
expect(cache).toEqual(true)
})

test('maps SPA root to s3 bucket root without subpath', () => {
const actual = normalizeRequest(new Request('https://fonts.example.com/'), TEST_ROUTES)
expect(actual.url).toEqual('https://s3.us-east-1.amazonaws.com/fonts.example.com/index.html')
const { request, cache } = normalizeRequest(new Request('https://fonts.example.com/'), TEST_ROUTES)
expect(request.url).toEqual('https://s3.us-east-1.amazonaws.com/fonts.example.com/index.html')
expect(cache).toEqual(true)
})

test('forwards original request when domain is not exact match', () => {
const actual = normalizeRequest(new Request('https://api.fonts.example.com/test/'), TEST_ROUTES)
expect(actual.url).toEqual('https://api.fonts.example.com/test/')
const { request, cache } = normalizeRequest(new Request('https://api.fonts.example.com/test/'), TEST_ROUTES)
expect(request.url).toEqual('https://api.fonts.example.com/test/')
expect(cache).toEqual(false)
})

test('simple path replace', () => {
const actual = normalizeRequest(new Request('https://example.com/old-path'), TEST_ROUTES)
expect(actual.url).toEqual('https://example.com/new-path')
const { request, cache } = normalizeRequest(new Request('https://example.com/old-path'), TEST_ROUTES)
expect(request.url).toEqual('https://example.com/new-path')
expect(cache).toEqual(true)
})
8 changes: 8 additions & 0 deletions test/utils/with-auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ test('it calls the callback when no deployments are defined', () => {
const config: Config = {
deployments: [],
routes: {},
edgeCacheTtl: 360
}
withAuth(event, config, callback)
expect(callback).toHaveBeenCalled()
Expand All @@ -48,6 +49,7 @@ test('it calls the callback when request method is options', () => {
MOCK_DEPLOYMENT_WITH_AUTH,
],
routes: {},
edgeCacheTtl: 360
}
withAuth(event, config, callback)
expect(callback).toHaveBeenCalled()
Expand All @@ -63,6 +65,7 @@ test('it calls the callback when a deployment is matched without auth', () => {
MOCK_DEPLOYMENT_WITHOUT_AUTH,
],
routes: {},
edgeCacheTtl: 360
}
withAuth(event, config, callback)
expect(callback).toHaveBeenCalled()
Expand All @@ -78,6 +81,7 @@ test('it does not call callback when there is no matching deployment', async ()
MOCK_DEPLOYMENT_WITH_AUTH,
],
routes: {},
edgeCacheTtl: 360
}
const { response } = await withAuth(event, config, callback)
expect(response.status).toBe(401)
Expand All @@ -95,6 +99,7 @@ test('it does not call the callback when auth is required but missing', async ()
MOCK_DEPLOYMENT_WITH_AUTH,
],
routes: {},
edgeCacheTtl: 360
}
const { response } = await withAuth(event, config, callback)
expect(response.status).toBe(401)
Expand All @@ -116,6 +121,7 @@ test('it does not call the callback when auth is required but username is incorr
MOCK_DEPLOYMENT_WITH_AUTH,
],
routes: {},
edgeCacheTtl: 360
}
const { response } = await withAuth(event, config, callback)
expect(response.status).toBe(401)
Expand All @@ -137,6 +143,7 @@ test('it does not call the callback when auth is required but password is incorr
MOCK_DEPLOYMENT_WITH_AUTH,
],
routes: {},
edgeCacheTtl: 360
}
const { response } = await withAuth(event, config, callback)
expect(response.status).toBe(401)
Expand All @@ -158,6 +165,7 @@ test('it calls callback when auth is required and valid', () => {
MOCK_DEPLOYMENT_WITH_AUTH,
],
routes: {},
edgeCacheTtl: 360
}
withAuth(event, config, callback)
expect(callback).toHaveBeenCalled()
Expand Down