Skip to content

Commit

Permalink
fix: handle static images
Browse files Browse the repository at this point in the history
  • Loading branch information
enapupe committed Jun 4, 2024
1 parent 3d8a2f6 commit 5c3783d
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 66 deletions.
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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, {
Expand Down
128 changes: 67 additions & 61 deletions app.js
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit 5c3783d

Please sign in to comment.