Last active
November 22, 2022 23:11
-
-
Save jahands/0fa8b0bfbcd80077984e3a162c5fce03 to your computer and use it in GitHub Desktop.
Serving a graph from Google Sheets, cached via Cloudflare R2 + Edge Cache for maximum uptime
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
async function statsGraph(params: Params, request: any, env: Env, ctx: ExecutionContext): Promise<Response> { | |
const graphKey = 'uuid.rocks/stats/graph.png' | |
const defaults = { | |
contentType: 'image/png', | |
cacheControl: 'public, max-age=1800', | |
} as const | |
const r2CacheTime = '2 hours' // itty-time format | |
// First check edge cache | |
// @ts-ignore | |
const cache = caches.default | |
const match = await cache.match(request) | |
if (match) { | |
console.log('Cache hit') | |
return match | |
} | |
const now = new Date().getTime() | |
// Check R2 for non-expired graph | |
let graph = await env.R2CACHE.get(graphKey) | |
// Create an error response that we will try to update | |
let response = new Response('internal error', { | |
status: 500, | |
headers: { 'Cache-Control': 'public, max-age=10' } | |
}) | |
if (!graph || parseInt(graph.customMetadata?.expires || '0') < now) { | |
// Refresh from upstream | |
const res = await fetch(env.UUID_GRAPH_URL) | |
// Read into buffer | |
const blob = await res.blob() | |
console.log('blob size', blob.size) | |
// Store in R2 if it's big enough (smaller graphs are probably errors) | |
if (blob.size > 10000) { // Should be an actual graph | |
const httpMetadata = { | |
contentType: res.headers.get('content-type') || defaults.contentType, | |
contentLength: blob.size.toString(), | |
cacheControl: defaults.cacheControl, | |
} | |
console.log('httpMetadata', httpMetadata) | |
const wrappedProm = async () => { | |
await env.R2CACHE.put(graphKey, await blob.arrayBuffer(), { | |
customMetadata: { expires: datePlus(r2CacheTime).getTime().toString() }, | |
httpMetadata | |
}) | |
return // void | |
} | |
// idk why waitUntil is like non-void promises.. | |
ctx.waitUntil(wrappedProm()) | |
response = new Response(await blob.arrayBuffer(), { | |
headers: { | |
'Content-Type': httpMetadata.contentType, | |
'Cache-Control': httpMetadata.cacheControl, | |
'Content-Length': httpMetadata.contentLength, | |
} | |
}) | |
} else if (graph) { | |
// Use the cached graph if it exists since we couldn't get a new one | |
console.log('Using cached graph because the new one was too small') | |
response = new Response(graph.body, { | |
headers: { | |
'Content-Type': graph.httpMetadata?.contentType || defaults.contentType, | |
'Cache-Control': graph.httpMetadata?.cacheControl || defaults.cacheControl, | |
'Content-Length': graph.size.toString(), | |
} | |
}) | |
} | |
// otherwise we'll send back the 500 error | |
} else { | |
console.log('serving from r2 cache!') | |
// Use the cached graph (it's still valid) | |
const headers = { | |
'Content-Type': graph.customMetadata?.contentType || defaults.contentType, | |
'Cache-Control': graph.customMetadata?.cacheControl || defaults.cacheControl, | |
} as Record<string, string> | |
if (graph.customMetadata?.contentLength) { | |
headers['Content-Length'] = graph.customMetadata?.contentLength | |
} | |
response = new Response(await graph.arrayBuffer(), { | |
headers | |
}) | |
} | |
ctx.waitUntil(cache.put(request, response.clone())) | |
return response | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment