Redirect not working when transforming via Workers onError event

Hi folks, I have a worker that resizes images; everything works well except when the animated GIF is too large, which returns an error. I'm trying to redirect upon encountering an error, but without success. What I'm doing wrong? The code:
async function handleRequest(request) {
let url = new URL(request.url);
let options = { cf: { image: { onerror: 'redirect' }}};

if (url.searchParams.has('width')) {
options.cf.image.width = url.searchParams.get('width');
}

if (url.searchParams.has('fit')) {
options.cf.image.fit = url.searchParams.get('fit');
}

if (url.searchParams.has('height')) {
options.cf.image.height = url.searchParams.get('height');
}

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 imagePath = url.pathname.slice(1);
if (!imagePath) {
return new Response('No image path specified', { status: 400 });
}

const imageRequest = new Request(`https://pub-8eca27df6a304971abd1fe716bc29b.r2.dev/files/${imagePath}`, { headers: request.headers });

const response = await fetch(imageRequest, options);

return new Response(response.body, { status: response.status, headers: response.headers });
}
async function handleRequest(request) {
let url = new URL(request.url);
let options = { cf: { image: { onerror: 'redirect' }}};

if (url.searchParams.has('width')) {
options.cf.image.width = url.searchParams.get('width');
}

if (url.searchParams.has('fit')) {
options.cf.image.fit = url.searchParams.get('fit');
}

if (url.searchParams.has('height')) {
options.cf.image.height = url.searchParams.get('height');
}

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 imagePath = url.pathname.slice(1);
if (!imagePath) {
return new Response('No image path specified', { status: 400 });
}

const imageRequest = new Request(`https://pub-8eca27df6a304971abd1fe716bc29b.r2.dev/files/${imagePath}`, { headers: request.headers });

const response = await fetch(imageRequest, options);

return new Response(response.body, { status: response.status, headers: response.headers });
}
19 Replies
Chaika
Chaika4mo ago
You shouldn't be using pub-r2.dev URLs. They're not meant for production and are rate-limited The onerror option has this description
In case of a fatal error that prevents the image from being resized, redirects to the unresized source image URL. This may be useful in case some images require user authentication and cannot be fetched anonymously via Worker. This option should not be used if there is a chance the source image is very large. This option is ignored if the image is from another domain, but you can use it with subdomains. Example:
You should be using an R2 Custom Domain anyway, so try using one and then see if that option works.
Nuno
Nuno4mo ago
Thanks for the help. I will try it. Again thank you! When I switch to the custom domain upon encountering an error, it returns: 'Error 1014 - CNAME Cross-User Banned.' However, the images that don't return an error work well...
Chaika
Chaika4mo ago
Interesting.. it redirects you to the source image on the R2 Custom Domain, and you get that error? Can you access it directly instead of through the redirect, and it works?
Nuno
Nuno4mo ago
No, the URL remains the same, so I think it doesn't execute the redirect. I can access the file through the R2 custom domain (s.example.com), but the worker uses a different custom domain (images.example.com). Therefore, when I request images.example.com/big-gif-file, it returns 'Error 1014 - CNAME Cross-User Banned'.
Chaika
Chaika4mo ago
how big is your GIF? I haven't played around with the error stuff too much, but for me it just gives up and returns fine with these warnings
No description
Chaika
Chaika4mo ago
that's an ~80mb gif
Chaika
Chaika4mo ago
what settings are you trying to use with it? a specific width/height/fit? opps nvm I get it on that gif as well, must be too small to give up on right away
Nuno
Nuno4mo ago
I tried with many... all of them return this error: ERROR 9413: Could not resize the image: The animation is too large. Animation resolution is 2000×1000, and at this size the maximum number of animation frames allowed is 88 (total area of all frames together must be less than 100000000), but the animation is longer than this What I want in these cases is to bypass the original file, or find a workaround to convert it to MP4 in the future
Chaika
Chaika4mo ago
Do you want an actual http redirect, or just seamlessly showing the non-modified file?
Nuno
Nuno4mo ago
just showing the non-modified file is ok
Chaika
Chaika4mo ago
yea it looks like the onerror stuff conflicts with R2 Custom Domains/the CF for SaaS setup. I was trying to see if I could get it to play nice. Might just need to either redirect to source or do a second fetch request if it fails which is silly
Nuno
Nuno4mo ago
Thank you for your time. I'd like to buy you a beer as a token of my appreciation 🙂
Chaika
Chaika4mo ago
thanks I'm good though, didn't even solve the issue lol. One workaround may be to use the URL format fetching through the Worker (ex, have the worker call /cdn-cgi/image/width=1800,quality=75,format=auto/image), but onerror=redirect quite literally redirects the user. You could accomplish the same in your worker by checking for the cross-domain error (which is silly but works)
if (response.status == 403) { // replace with your url
return Response.redirect(`https://i.chaika.me/${imagePath}`, 302)
}
if (response.status == 403) { // replace with your url
return Response.redirect(`https://i.chaika.me/${imagePath}`, 302)
}
or by fetching the original to seamlessly return the non-resized on failure
if (response.status == 403) {
return fetch(`https://i.chaika.me/${imagePath}`)
}
if (response.status == 403) {
return fetch(`https://i.chaika.me/${imagePath}`)
}
no matter what you're not going to be able to avoid a second request, although if you have caching setup right on your Custom domain, which it looks like you do, that second request is going to be super cheap/from cache
Nuno
Nuno4mo ago
Hmm I will try. But like you said this is a bug right? Is expected that it will be fixed soon or not? I think this is a good fix for now. Thank you for your time. 🙂
Chaika
Chaika4mo ago
I wouldn't hold your breath but I can make a report and see. It is really strange behavior but understandable from the viewpoint that CF for SaaS (which R2 Custom Domains use) are weird to begin with and this isn't the first feature to have issues with it like this. The whole thing of CF for SaaS applying two zone's/website's settings (yours + r2.dev's in this case) and linking requests over is a bunch of weird magic
Nuno
Nuno4mo ago
eheh ok anyway, thank you
Chaika
Chaika4mo ago
turns out the onerror option doesn't seem to exist/work in Workers at all, despite the documentation. It's not in worker-types and doesn't work with a normal origin. I reported that first lol There is an interesting section about error handling here: https://developers.cloudflare.com/images/transform-images/transform-via-workers/#error-handling nothing too helpful though
Nuno
Nuno4mo ago
Thanks! Please send me a message, as I would like to talk with you about a code review. I'm seeking your help to validate and improve my worker.