in packages/cloudflare/src/main.ts [58:293]
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const allowedMethods = ['GET', 'HEAD', 'OPTIONS'];
const origin = request.headers.get('origin');
if (allowedMethods.indexOf(request.method) === -1)
return new Response('Method Not Allowed', { status: 405 });
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
allow: allowedMethods.join(', '),
'Access-Control-Allow-Methods': allowedMethods.join(', '),
...generateAccessControlAllowOriginPolicy(env, origin),
Vary: 'origin',
},
});
}
let triedIndex = false;
const url = new URL(request.url);
const healthCheck = isHealthCheck(env, url);
if (healthCheck) {
if (
env.HEALTHCHECK_UA_REGEXP &&
!request.headers.get('user-agent')?.match(env.HEALTHCHECK_UA_REGEXP)
) {
return new Response('File Not Found', { status: 404 });
}
url.pathname = '/ping';
}
// Trim the first part of the request
url.hostname = url.hostname.replace(/^[^.]+\./, '');
let response: Response | undefined;
if (DEBUG) {
// eslint-disable-next-line no-console
console.debug(`Request URL: ${url.toString()}`);
}
const shouldCache = env.CACHE_CONTROL !== 'no-store' && !healthCheck;
const cacheKey = new Request(url.toString(), request);
const cache = caches.default;
if (shouldCache) {
response = await cache.match(cacheKey);
}
let range: ParsedRange | undefined;
if (!response || !(response.ok || response.status === 304)) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.warn(`Cache MISS for ${url.toString()}`);
}
let path = (env.PATH_PREFIX || '') + decodeURIComponent(url.pathname);
if (path.endsWith('/')) {
if (env.INDEX_FILE && env.INDEX_FILE !== '') {
path += env.INDEX_FILE;
triedIndex = true;
}
}
if (path !== '/' && path.startsWith('/')) {
path = path.substring(1);
}
let file: R2Object | R2ObjectBody | null | undefined;
// Range handling
if (request.method === 'GET') {
const rangeHeader = request.headers.get('range');
if (rangeHeader) {
file = await env.R2_BUCKET.head(path);
if (!file) return new Response('File Not Found', { status: 404 });
const parsedRanges = parseRange(file.size, rangeHeader);
// R2 only supports 1 range at the moment, reject if there is more than one
if (
parsedRanges !== -1 &&
parsedRanges !== -2 &&
parsedRanges.length === 1 &&
parsedRanges.type === 'bytes'
) {
const firstRange = parsedRanges[0];
range =
file.size === firstRange.end + 1
? { suffix: file.size - firstRange.start }
: {
offset: firstRange.start,
length: firstRange.end - firstRange.start + 1,
};
} else {
return new Response('Range Not Satisfiable', { status: 416 });
}
}
}
// Etag/If-(Not)-Match handling
// R2 requires that etag checks must not contain quotes, and the S3 spec only allows one etag
// This silently ignores invalid or weak (W/) headers
const getHeaderEtag = (header: string | null) => header?.trim().replace(/^['"]|['"]$/g, '');
const ifMatch = getHeaderEtag(request.headers.get('if-match'));
const ifNoneMatch = getHeaderEtag(request.headers.get('if-none-match'));
const ifModifiedSince = Date.parse(request.headers.get('if-modified-since') || '');
const ifUnmodifiedSince = Date.parse(request.headers.get('if-unmodified-since') || '');
const ifRange = request.headers.get('if-range');
if (range && ifRange && file) {
const maybeDate = Date.parse(ifRange);
if (Number.isNaN(maybeDate) || new Date(maybeDate) > file.uploaded) {
// httpEtag already has quotes, no need to use getHeaderEtag
if (ifRange.startsWith('W/') || ifRange !== file.httpEtag) range = undefined;
}
}
if (ifMatch || ifUnmodifiedSince) {
file = await env.R2_BUCKET.get(path, {
onlyIf: {
etagMatches: ifMatch,
uploadedBefore: ifUnmodifiedSince ? new Date(ifUnmodifiedSince) : undefined,
},
range,
});
if (file && !hasBody(file)) {
return new Response('Precondition Failed', { status: 412 });
}
}
if (ifNoneMatch || ifModifiedSince) {
// if-none-match overrides if-modified-since completely
if (ifNoneMatch) {
file = await env.R2_BUCKET.get(path, {
onlyIf: { etagDoesNotMatch: ifNoneMatch },
range,
});
} else if (ifModifiedSince) {
file = await env.R2_BUCKET.get(path, {
onlyIf: { uploadedAfter: new Date(ifModifiedSince) },
range,
});
}
if (file && !hasBody(file)) {
return new Response(null, { status: 304 });
}
}
if (request.method === 'HEAD') {
file = await env.R2_BUCKET.head(path);
} else if (!file || !hasBody(file)) {
file = await env.R2_BUCKET.get(path, { range });
}
let notFound: boolean = false;
if (file === null) {
if (env.INDEX_FILE && triedIndex) {
// remove the index file since it doesnt exist
path = path.substring(0, path.length - env.INDEX_FILE.length);
}
if (env.NOTFOUND_FILE && env.NOTFOUND_FILE !== '') {
notFound = true;
path = env.NOTFOUND_FILE;
file =
request.method === 'HEAD'
? await env.R2_BUCKET.head(path)
: await env.R2_BUCKET.get(path);
}
// if its still null, either 404 is disabled or that file wasn't found either
// this isn't an else because then there would have to be two of them
if (file == null) {
return new Response('File Not Found', { status: 404 });
}
}
let responseStatus;
if (notFound) {
responseStatus = 404;
} else if (range) {
responseStatus = 206;
} else {
responseStatus = 200;
}
let contentLength = file?.size;
if (range && !notFound) {
if (rangeHasLength(range)) {
contentLength = range.length;
} else if (range.suffix) {
contentLength = range.suffix;
}
}
response = new Response(hasBody(file) && file.size !== 0 ? file.body : null, {
status: responseStatus,
headers: {
'accept-ranges': 'bytes',
...generateAccessControlAllowOriginPolicy(env, origin),
etag: notFound ? '' : file?.httpEtag,
// if the 404 file has a custom cache control, we respect it
'cache-control':
file?.httpMetadata?.cacheControl ??
(notFound || healthCheck ? '' : env.CACHE_CONTROL || ''),
expires: file?.httpMetadata?.cacheExpiry?.toUTCString() ?? '',
'last-modified': notFound ? '' : file?.uploaded.toUTCString(),
'content-encoding': file?.httpMetadata?.contentEncoding ?? '',
'content-type': file?.httpMetadata?.contentType ?? 'application/octet-stream',
'content-language': file?.httpMetadata?.contentLanguage ?? '',
'content-disposition': file?.httpMetadata?.contentDisposition ?? '',
'content-range': range && !notFound ? getRangeHeader(range, file?.size || 0) : '',
'content-length': contentLength?.toString(),
'cross-origin-resource-policy': 'cross-origin',
Vary: 'origin',
},
});
if (request.method === 'GET' && !range && shouldCache && !notFound)
ctx.waitUntil(cache.put(cacheKey, response.clone()));
} else if (DEBUG) {
// eslint-disable-next-line no-console
console.info(`Cache HIT for ${url.toString()}`);
}
return response;
},