From 5c3783d15e17028e3f372ef5dd9c38fd5487d56b Mon Sep 17 00:00:00 2001 From: Iacami Gevaerd Date: Tue, 4 Jun 2024 10:12:25 -0300 Subject: [PATCH] fix: handle static images --- README.md | 28 +++++++++--- app.js | 128 ++++++++++++++++++++++++++++-------------------------- 2 files changed, 90 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index db15704..cd975ff 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,27 @@ This project has been replaced by the following CloudFlare worker: ``` - addEventListener("fetch", (event) => { event.respondWith(handleRequest(event.request)); }); -const getBucket = (hostname) => { - if (hostname === "stg-media.openbeta.io") { +const getBaseURL = (hostname, pathname, referer) => { + const isStaging = hostname === "stg-media.openbeta.io" + const isStatic = pathname.startsWith("/_next/static") + // handling static frontend content + if (isStatic) { + // we use the referer to keep vercel previews working + if (referer){ + return referer.replace(/\/$/, ""); + } + // otherwise we route to the standard staging/prod base url + if (isStaging){ + return "https://stg.openbeta.io"; + } + return "https://openbeta.io"; + } + // handling other photos stored in google + if (isStaging) { return "https://storage.googleapis.com/openbeta-staging"; } return "https://storage.googleapis.com/openbeta-prod"; @@ -49,8 +63,12 @@ async function handleRequest(request) { options.cf.image.format = "webp"; } - const bucket = getBucket(url.hostname); - const imageURL = `${bucket}${url.pathname}`; + const baseURL = getBaseURL( + url.hostname, + url.pathname, + request.headers.get("referer"), + ); + const imageURL = `${baseURL}${url.pathname}`; // Build a request that passes through request headers const imageRequest = new Request(imageURL, { diff --git a/app.js b/app.js index 93773c6..c8a6516 100644 --- a/app.js +++ b/app.js @@ -1,71 +1,77 @@ -const express = require("express"); -const sharp = require("sharp"); -const { Storage, ApiError } = require("@google-cloud/storage"); - -const storage = new Storage(); -const app = express(); - -const PORT = 8080; -const HOST = "0.0.0.0"; -const BASE_STORAGE_IMAGE_URL = "https://storage.googleapis.com/"; -const BUCKET = process.env.STORAGE_BUCKET || "openbeta-prod"; - -const getFormat = (webp, avif) => { - return webp ? "webp" : avif ? "avif" : "jpeg"; -}; - -app.get("/healthy", (req, res) => { - res.send("yep."); -}); - -app.get("/version", (req, res) => { - res.send(process.env.APP_VERSION); +addEventListener("fetch", (event) => { + event.respondWith(handleRequest(event.request)); }); -app.get("*", async (req, res) => { - try { - if (!/\.(jpe?g|png|gif|webp)$/i.test(req.path)) { - return res.status(400).send("Disallowed file extension"); +const getBaseURL = (hostname, pathname, referer) => { + const isStaging = hostname === "stg-media.openbeta.io"; + const isStatic = pathname.startsWith("/_next/static"); + // handling static frontend content + if (isStatic) { + // we use the referer to keep vercel previews working + if (referer) { + return referer.replace(/\/$/, ""); + } + // otherwise we route to the standard staging/prod base url + if (isStaging) { + return "https://stg.openbeta.io"; } + return "https://openbeta.io"; + } + // handling other photos stored in google + if (isStaging) { + return "https://storage.googleapis.com/openbeta-staging"; + } + return "https://storage.googleapis.com/openbeta-prod"; +}; - const webp = req.headers.accept?.includes("image/webp"); - const avif = req.headers.accept?.includes("image/avif"); - const quality = Number(req.query.q) || 90; - const width = Number(req.query.w) || undefined; - const height = Number(req.query.h) || undefined; - const format = getFormat(webp, avif); +/** + * Fetch and log a request + * @param {Request} request + */ +async function handleRequest(request) { + // Parse request URL to get access to query string + const url = new URL(request.url); - res - .set("Cache-Control", "public, max-age=15552000") - .set("Vary", "Accept") - .type(`image/${format}`); + if (url.pathname.length < 2) + return new Response("Missing image", { status: 400 }); + if (!/\.(jpe?g|png|gif|webp)$/i.test(url.pathname)) { + return new Response("Disallowed file extension", { status: 400 }); + } - const pipeline = sharp(); + // Cloudflare-specific options are in the cf object. + let options = { cf: { image: {} } }; - storage - .bucket(BUCKET) - .file(req.path.slice(1)) // remove leading slash - .createReadStream() - .on("error", function (e) { - if (e instanceof ApiError) { - if (e.message?.includes("No such object")) - return res.status(404).end(); - } - return res.status(500).send(JSON.stringify(e)); - }) - .pipe(pipeline); + // Copy parameters from query string to request options. + // You can implement various different parameters here. + if (url.searchParams.has("w")) + options.cf.image.width = url.searchParams.get("w"); + if (url.searchParams.has("q")) + options.cf.image.quality = url.searchParams.get("q"); + if (url.searchParams.has("h")) + options.cf.image.height = url.searchParams.get("h"); - pipeline - .rotate() - .resize({ width, height }) - .toFormat(format, { effort: 3, quality, progressive: true }) - .pipe(res); - } catch (e) { - return res.status(500).send(JSON.stringify(e)); + // Your Worker is responsible for automatic format negotiation. Check the Accept header. + const accept = request.headers.get("Accept"); + if (/image\/avif/.test(accept)) { + options.cf.image.format = "avif"; + } else if (/image\/webp/.test(accept)) { + options.cf.image.format = "webp"; } -}); -const port = parseInt(process.env.PORT) || PORT; -app.listen(port, HOST, () => { - console.log(`Running on http://${HOST}:${port}`); -}); + const baseURL = getBaseURL( + url.hostname, + url.pathname, + request.headers.get("referer"), + ); + const imageURL = `${baseURL}${url.pathname}`; + + // Build a request that passes through request headers + const imageRequest = new Request(imageURL, { + headers: request.headers, + }); + + let response = await fetch(imageRequest, options); + response = new Response(response.body, response); + response.headers.set("Cache-Control", "public, max-age=15552000"); + return response; +}