diff --git a/src/constants/limits.ts b/src/constants/limits.ts index 501e596..4dc1ea3 100644 --- a/src/constants/limits.ts +++ b/src/constants/limits.ts @@ -1,7 +1,7 @@ /** * Max amount of retries for S3 requests */ -export const S3_RETRY_LIMIT = 3; +export const R2_RETRY_LIMIT = 3; /** * Max amount of keys to be returned in a S3 request diff --git a/src/handlers/strategies/directoryListing.ts b/src/handlers/strategies/directoryListing.ts index 3eaa002..0cf4919 100644 --- a/src/handlers/strategies/directoryListing.ts +++ b/src/handlers/strategies/directoryListing.ts @@ -12,7 +12,7 @@ import { getFile } from './serveFile'; // Imports the Precompiled Handlebars Template import htmlTemplate from '../../templates/directoryListing.out.js'; -import { S3_MAX_KEYS, S3_RETRY_LIMIT } from '../../constants/limits'; +import { S3_MAX_KEYS, R2_RETRY_LIMIT } from '../../constants/limits'; // Applies the Template into a Handlebars Template Function const handleBarsTemplate = Handlebars.template(htmlTemplate); @@ -152,7 +152,7 @@ async function fetchR2Result( cursor: string | undefined, env: Env ): Promise { - let retriesRemaining = S3_RETRY_LIMIT; + let retriesRemaining = R2_RETRY_LIMIT; while (retriesRemaining > 0) { try { // Send request to R2 diff --git a/src/handlers/strategies/serveFile.ts b/src/handlers/strategies/serveFile.ts index 6c10244..91835a3 100644 --- a/src/handlers/strategies/serveFile.ts +++ b/src/handlers/strategies/serveFile.ts @@ -1,6 +1,7 @@ import { Env } from '../../env'; import { objectHasBody } from '../../util'; import responses from '../../commonResponses'; +import { R2_RETRY_LIMIT } from '../../constants/limits'; /** * Decides on what status code to return to @@ -42,6 +43,57 @@ function getStatusCode(request: Request, objectHasBody: boolean): number { return 304; } +/** + * Fetch an object from a R2 bucket with retries + * The bindings _might_ have retries, if so this just adds + * a little bit more resiliency + * @param bucket The {@link R2Bucket} to read from + * @param key Object key + * @param options Conditional headers, etc. + */ +async function r2GetWithRetries( + bucket: R2Bucket, + key: string, + options?: R2GetOptions +): Promise { + for (let i = 0; i < R2_RETRY_LIMIT; i++) { + try { + return await bucket.get(key, options); + } catch (err) { + // Log error & retry + console.error(`R2 GetObject error: ${err}`); + } + } + + // R2 isn't having a good day, return a 500 & log to sentry + throw new Error(`R2 GetObject failed after ${R2_RETRY_LIMIT} retries: `); +} + +/** + * Fetch an object from a R2 bucket with retries + * The bindings _might_ have retries, if so this just adds + * a little bit more resiliency + * @param bucket The {@link R2Bucket} to read from + * @param key Object key + */ +async function r2HeadWithRetries( + bucket: R2Bucket, + key: string +): Promise { + for (let i = 0; i < R2_RETRY_LIMIT; i++) { + try { + const file = await bucket.head(key); + return file; + } catch (err) { + // Log error & retry + console.error(`R2 HeadObject error: ${err}`); + } + } + + // R2 isn't having a good day, return a 500 & log to sentry + throw new Error(`R2 HeadObject failed after ${R2_RETRY_LIMIT} retries: `); +} + /** * File handler * @param url Parsed url of the request @@ -60,7 +112,7 @@ export async function getFile( switch (request.method) { case 'GET': try { - file = await env.R2_BUCKET.get(bucketPath, { + file = await r2GetWithRetries(env.R2_BUCKET, bucketPath, { onlyIf: request.headers, range: request.headers, }); @@ -71,7 +123,7 @@ export async function getFile( return responses.BAD_REQUEST; } case 'HEAD': - file = await env.R2_BUCKET.head(bucketPath); + file = await r2HeadWithRetries(env.R2_BUCKET, bucketPath); break; default: return responses.METHOD_NOT_ALLOWED;