Thank you so much for trying, and I’m impressed you got it to work.
It’s quite large, but maybe the devil is in the details of what I did not show before.
I’ve include the whole worker below. I’m basically rolling my own URL-based parameter system, with some built-in presets. Much of my worker’s early code is doing all that parsing and handling. For now, the attempted JSON output is only sent to the console, but it would eventually be used in the overlay logic.
/**
* https://my-worker.image.workers.dev
* Fetch and log a request
* @param {Request} request
* From https://github.com/fransallen/image-resizing/blob/master/index.js
*/
addEventListener("fetch", (event) => {
event.respondWith(
handleRequest(event.request).catch(
(err) => new Response(err.stack, { status: 500 })
)
);
});
async function handleRequest(request) {
// Get URL of the original (full size) image to resize.
const ORIGIN = 'https://www.domain.com/'; // origin of the images, with trailing slash
// Parse request URL to get access to query string
const url = new URL(request.url);
const path = url.pathname; // get path and file part of URL
console.log('path: '+path);
if (path == '/favicon.ico')
{ // if request is for the favicon, silently redirect to main site's favicon
let response = await fetch(ORIGIN+'favicon.ico');
response = new Response(response.body, response);
response.headers.set(
'Cache-Control',
'public, max-age=0, immutable',
);
response.headers.set('Vary', 'Accept');
return response;
// a 301 redirect is a waste of client work
// return Response.redirect(ORIGIN+'favicon.ico', 301);
}
// Cloudflare-specific options are in the cf object.
const options = { cf: { image: {} } };
// Set defaults
options.cf.image.width = 600; // default to something not too monstrous
options.cf.image.fit = 'scale-down';
options.cf.image.sharpen = 0.6;
options.cf.image.quality = 95;
options.cf.image.background = '#FFFFFF';
// console.log(url);
// 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('h'))
options.cf.image.height = url.searchParams.get('h');
if (url.searchParams.has('f'))
options.cf.image.format = url.searchParams.get('f');
if (url.searchParams.has('q'))
options.cf.image.quality = url.searchParams.get('q');
if (url.searchParams.has('bg'))
options.cf.image.background = url.searchParams.get('bg');
if (url.searchParams.has('dpr'))
options.cf.image.dpr = url.searchParams.get('dpr');
if (url.searchParams.has('fit'))
options.cf.image.fit = url.searchParams.get('fit');
if (url.searchParams.has('rotate'))
options.cf.image.rotate = url.searchParams.get('rotate');
if (url.searchParams.has('sharpen'))
options.cf.image.sharpen = url.searchParams.get('sharpen');
if (url.searchParams.has('gravity'))
options.cf.image.gravity = url.searchParams.get('gravity');
if (url.searchParams.has('metadata'))
options.cf.image.metadata = url.searchParams.get('metadata');
const re = /^\/?(.*)\/(([0-9]+)(-[0-9])?\.jpg)$/;
const pathparts = re.exec(url.pathname); // get slashy path and ending JPG filename
// console.log('pathparts: '+pathparts);
let pathslashes = pathparts[1]; // get slashy parts of path
const pathfile = pathparts[2]; // get ending filename
// console.log('pathfile: '+pathfile);
pathslashes = pathslashes.split('/'); // get array of slashy parameters in form: /param/
console.log('pathslashes: '+pathslashes);
// first look for presets
if (/^[A-Z]+[^-]/i.test(pathslashes[0])) // if start with a letter but no hyphen
{
const preset = pathslashes.shift(); // extract preset
console.log('Found preset: '+preset);
switch(preset) // dispatch on preset
{
case 'thumb122': // smallest listing thumbs
options.cf.image.width = undefined;
options.cf.image.height = 122;
break;
case 'thumb140': // small thumbs on item page
options.cf.image.width = undefined;
options.cf.image.height = 140;
break;
case 'medium': // common medium image
options.cf.image.width = 268;
break;
case 'square268': // square cropped for PCGS mailing
options.cf.image.width = 268;
options.cf.image.height = 268;
options.cf.image.fit = 'crop';
options.cf.image.gravity = 'bottom';
break;
default:
}
}
// look for width and height
if (/^([0-9])+$/.test(pathslashes[0])) // if first match is a number, it's a width
{
options.cf.image.width = pathslashes.shift(); // extract width
console.log('Found width: '+options.cf.image.width);
if (options.cf.image.width == 0) options.cf.image.width = undefined;
}
if (/^([0-9])+$/.test(pathslashes[0])) // if second match is a number, it's a height
{
options.cf.image.height = pathslashes.shift(); // extract height
console.log('Found height: '+options.cf.image.height);
if (options.cf.image.height == 0) options.cf.image.height = undefined;
}
// look in rest of URL for "p-" type parameters
let param;
for (let pathslash of pathslashes) {
console.log(pathslash);
if ((param = /^([A-Z]+)-(.*)$/i.exec(pathslash)))
{ // if start with letters and hyphen parameter delim
console.log('Found param: '+param);
let name = param[1].toLowerCase(); // get parameter name in lower case
let value = param[2]; // get paramter value
switch(name) // dispatch on param name
{
case 'w':
options.cf.image.width = value;
break;
case 'h':
options.cf.image.height = value;
break;
case 'f':
options.cf.image.fit = value;
break;
case 'g':
options.cf.image.gravity = value;
break;
case 't':
options.cf.image.trim = value;
break;
case 'q':
options.cf.image.quality = value;
break;
case 'm':
options.cf.image.metadata = value;
break;
case 'b':
options.cf.image.background = value;
break;
case 'r':
options.cf.image.rotate = value;
break;
case 's':
options.cf.image.sharpen = value;
break;
default:
}
}
}
const imageURL = ORIGIN + 'getrawimage.php?id=' + pathfile; // get origin server's full path to JPG image
console.log('imageURL: '+imageURL);
console.log('Size: '+options.cf.image.width+' x '+options.cf.image.height);
// Build a request that passes through request headers,
// so that automatic format negotiation can work.
const imageRequest = new Request(imageURL, {
headers: request.headers,
});
// pre-fetch to find image info... doesn't work
const options_json = { cf: { image: {} } };
options_json.cf.image.format = 'json';
let response_json = await fetch(imageRequest, options_json);
console.log('Fetch JSON response ok: '+response_json.ok+', status: '+response_json.status);
if (response_json.ok || response_json.status == 304) {
console.log('Fetch JSON function response: '+JSON.stringify(response_json));
// let json = await response_json.json();
console.log('Fetch JSON body response: '+await response_json.text()); // JSON.stringify()
}
// Returning fetch() with resizing options will pass through response with the resized image.
let response = await fetch(imageRequest, options);
// console.log('Fetch JSON body response: '+await response.text()); // JSON.stringify()
// Reconstruct the Response object to make its headers mutable.
response = new Response(response.body, response);
if (response.ok || response.status == 304) {
// Set cache for 1 year
'Cache-Control',
// 'public, max-age=31536000, immutable',
// console.log('Fetch/resize image response: '+JSON.stringify(response));
response.headers.set(
'Cache-Control',
'public, max-age=0, immutable',
);
// Set Vary header
response.headers.set('Vary', 'Accept');
return response;
} else {
return new Response(
`Could not fetch the image — the server returned HTTP error:
status: ${response.status}
statusText: ${response.statusText}
headers: ${JSON.stringify(response.headers)}
redirected: ${response.redirected}
url: ${response.url}
webSocket: ${response.webSocket}
... in response to a request to ${imageURL}`,
{
status: 400,
headers: {
'Cache-Control': 'no-cache',
},
},
);
}
}