Skip to content

Instantly share code, notes, and snippets.

@jahands
Last active November 22, 2022 23:11
Show Gist options
  • Save jahands/0fa8b0bfbcd80077984e3a162c5fce03 to your computer and use it in GitHub Desktop.
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
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