Refactor code to improve performance and readability

This commit is contained in:
abersheeran 2023-12-18 12:07:26 +08:00
parent 0e6f45e368
commit 4295fddbdb

View File

@ -78,35 +78,33 @@ function fromR2Object(object: R2Object | null | undefined): DavProperties {
};
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const { bucket } = env;
if (request.headers.get('Authorization') !== `Basic ${btoa(`${env.USERNAME}:${env.PASSWORD}`)}`) {
return new Response('Unauthorized', {
status: 401, headers: {
'WWW-Authenticate': 'Basic realm="webdav"',
}
});
}
function make_resource_path(request: Request): string {
return new URL(request.url).pathname.slice(1)
}
let response: Response;
let resource_path = new URL(request.url).pathname.slice(1);
switch (request.method) {
case 'OPTIONS': {
response = new Response(null, {
async function handle_options(request: Request, bucket: R2Bucket): Promise<Response> {
return new Response(null, {
status: 204,
headers: {
'DAV': DAV_CLASS,
'Allow': SUPPORT_METHODS.join(', '),
}
});
}
break;
case 'HEAD':
case 'GET': {
}
async function handle_head(request: Request, bucket: R2Bucket): Promise<Response> {
let response = await handle_get(request, bucket);
return new Response(null, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
}
async function handle_get(request: Request, bucket: R2Bucket): Promise<Response> {
let resource_path = make_resource_path(request);
if (request.url.endsWith('/')) {
let r2_objects = await bucket.list({
prefix: resource_path,
@ -119,7 +117,7 @@ export default {
let href = `/${object.key + (object.customMetadata?.resourcetype === '<collection />' ? '/' : '')}`;
page += `<a href="${href}">${object.httpMetadata?.contentDisposition ?? object.key}</a><br>`;
}
response = new Response(page, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
return new Response(page, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
} else {
let object = await bucket.get(resource_path, {
onlyIf: request.headers,
@ -131,11 +129,11 @@ export default {
}
if (object === null) {
response = new Response('Not Found', { status: 404 });
return new Response('Not Found', { status: 404 });
} else if (!isR2ObjectBody(object)) {
response = new Response("Precondition Failed", { status: 412 });
return new Response("Precondition Failed", { status: 412 });
} else {
response = new Response(object.body, {
return new Response(object.body, {
status: object.range ? 206 : 200,
headers: {
'Content-Type': object.httpMetadata?.contentType ?? 'application/octet-stream',
@ -160,12 +158,13 @@ export default {
});
}
}
}
break;
case 'PUT': {
}
async function handle_put(request: Request, bucket: R2Bucket): Promise<Response> {
let resource_path = make_resource_path(request);
if (resource_path.endsWith('/')) {
response = new Response('Method Not Allowed', { status: 405 });
break;
return new Response('Method Not Allowed', { status: 405 });
}
// Check if the parent directory exists
@ -173,8 +172,7 @@ export default {
if (dirpath !== '') {
let dir = await bucket.head(dirpath);
if (!(dir && dir.customMetadata?.resourcetype === '<collection />')) {
response = new Response('Conflict', { status: 409 });
break;
return new Response('Conflict', { status: 409 });
}
}
@ -183,28 +181,27 @@ export default {
onlyIf: request.headers,
httpMetadata: request.headers,
});
response = new Response('', { status: 201 });
}
break;
case 'DELETE': {
return new Response('', { status: 201 });
}
async function handle_delete(request: Request, bucket: R2Bucket): Promise<Response> {
let resource_path = make_resource_path(request);
if (!resource_path.endsWith('/')) {
let resource = await bucket.head(resource_path);
if (resource === null) {
response = new Response('Not Found', { status: 404 });
break;
return new Response('Not Found', { status: 404 });
} else {
if (resource.customMetadata?.resourcetype !== '<collection />') {
await bucket.delete(resource_path);
response = new Response(null, { status: 204 });
break;
return new Response(null, { status: 204 });
}
}
}
let dirpath = resource_path.slice(0, -1);
if (await bucket.head(dirpath) === null) {
response = new Response('Not Found', { status: 404 });
break;
return new Response('Not Found', { status: 404 });
}
await bucket.delete(dirpath);
@ -225,44 +222,44 @@ export default {
}
} while (r2_objects.truncated);
response = new Response(null, { status: 204 });
}
break;
case 'MKCOL': {
return new Response(null, { status: 204 });
}
async function handle_mkcol(request: Request, bucket: R2Bucket): Promise<Response> {
let resource_path = make_resource_path(request);
if (request.body) {
response = new Response('Unsupported Media Type', { status: 415 });
break;
return new Response('Unsupported Media Type', { status: 415 });
}
resource_path = resource_path.endsWith('/') ? resource_path.slice(0, -1) : resource_path;
// Check if the resource already exists
if (await bucket.head(resource_path)) {
response = new Response('Method Not Allowed', { status: 405 });
break;
return new Response('Method Not Allowed', { status: 405 });
}
// Check if the parent directory exists
let parent_dir = resource_path.split('/').slice(0, -1).join("/");
if (parent_dir !== '' && !await bucket.head(parent_dir)) {
response = new Response('Conflict', { status: 409 });
break;
return new Response('Conflict', { status: 409 });
}
await bucket.put(resource_path, new Uint8Array(), {
httpMetadata: request.headers,
customMetadata: { resourcetype: '<collection />' }
});
response = new Response('', { status: 201 });
}
break;
case 'PROPFIND': {
return new Response('', { status: 201 });
}
async function handle_propfind(request: Request, bucket: R2Bucket): Promise<Response> {
let resource_path = make_resource_path(request);
let depth = request.headers.get('Depth') ?? 'infinity';
switch (depth) {
case '0': {
if (resource_path === "") {
response = new Response(`<?xml version="1.0" encoding="utf-8"?>
return new Response(`<?xml version="1.0" encoding="utf-8"?>
<multistatus xmlns="DAV:">
<response>
<href>/</href>
@ -280,7 +277,6 @@ export default {
'Content-Type': 'text/xml',
},
});
break;
}
let object = await bucket.head(resource_path);
@ -289,8 +285,7 @@ export default {
}
if (object === null) {
response = new Response('Not Found', { status: 404 });
break;
return new Response('Not Found', { status: 404 });
}
let page = `<?xml version="1.0" encoding="utf-8"?>
@ -310,14 +305,13 @@ export default {
</response>
</multistatus>
`;
response = new Response(page, {
return new Response(page, {
status: 207,
headers: {
'Content-Type': 'text/xml',
},
});
}
break;
case '1': {
if (resource_path !== "") {
let object = await bucket.head(resource_path);
@ -326,8 +320,7 @@ export default {
}
if (object === null) {
response = new Response('Not Found', { status: 404 });
break;
return new Response('Not Found', { status: 404 });
}
if (object.customMetadata?.resourcetype !== '<collection />') {
@ -348,13 +341,12 @@ export default {
</response>
</multistatus>
`;
response = new Response(page, {
return new Response(page, {
status: 207,
headers: {
'Content-Type': 'text/xml',
},
});
break;
}
}
@ -393,14 +385,13 @@ export default {
}
} while (r2_objects.truncated)
page += '\n</multistatus>\n';
response = new Response(page, {
return new Response(page, {
status: 207,
headers: {
'Content-Type': 'text/xml',
},
});
}
break;
case 'infinity': {
if (resource_path !== "") {
let object = await bucket.head(resource_path);
@ -409,8 +400,7 @@ export default {
}
if (object === null) {
response = new Response('Not Found', { status: 404 });
break;
return new Response('Not Found', { status: 404 });
}
if (object.customMetadata?.resourcetype !== '<collection />') {
@ -431,13 +421,12 @@ export default {
</response>
</multistatus>
`;
response = new Response(page, {
return new Response(page, {
status: 207,
headers: {
'Content-Type': 'text/xml',
},
});
break;
}
}
@ -475,34 +464,32 @@ export default {
}
} while (r2_objects.truncated);
page += '\n</multistatus>\n';
response = new Response(page, {
return new Response(page, {
status: 207,
headers: {
'Content-Type': 'text/xml',
},
});
}
break;
default: {
response = new Response('Forbidden', { status: 403 });
return new Response('Forbidden', { status: 403 });
}
}
}
break;
case 'COPY': {
}
async function handle_copy(request: Request, bucket: R2Bucket): Promise<Response> {
let resource_path = make_resource_path(request);
let dont_overwrite = request.headers.get('Overwrite') === 'F';
let destination_header = request.headers.get('Destination');
if (destination_header === null) {
response = new Response('Bad Request', { status: 400 });
break;
return new Response('Bad Request', { status: 400 });
}
let destination = new URL(destination_header).pathname.slice(1);
// Check if the parent directory exists
let destination_parent = destination.split('/').slice(0, destination.endsWith('/') ? -2 : -1).join('/');
if (destination_parent !== '' && !await bucket.head(destination_parent)) {
response = new Response('Conflict', { status: 409 });
break;
return new Response('Conflict', { status: 409 });
}
if (resource_path.endsWith('/')) {
@ -513,8 +500,7 @@ export default {
prefix: resource_path,
});
if (r2_objects.objects.length === 0) {
response = new Response('Not Found', { status: 404 });
break;
return new Response('Not Found', { status: 404 });
}
await Promise.all(r2_objects.objects.map(
object => (async () => {
@ -528,62 +514,56 @@ export default {
}
})()
));
response = new Response('', { status: 201 });
return new Response('', { status: 201 });
}
break;
case '0': {
let object = await bucket.get(resource_path.slice(0, -1));
if (object === null) {
response = new Response('Not Found', { status: 404 });
break;
return new Response('Not Found', { status: 404 });
}
await bucket.put(destination, object.body, {
httpMetadata: object.httpMetadata,
customMetadata: object.customMetadata,
});
response = new Response('', { status: 201 });
return new Response('', { status: 201 });
}
break;
default: {
response = new Response('Bad Request', { status: 400 });
return new Response('Bad Request', { status: 400 });
}
}
} else {
let destination_exists = await bucket.head(destination);
if (dont_overwrite && destination_exists) {
response = new Response('Precondition Failed', { status: 412 });
break;
return new Response('Precondition Failed', { status: 412 });
}
let src = await bucket.get(resource_path);
if (src === null) {
response = new Response('Not Found', { status: 404 });
break;
return new Response('Not Found', { status: 404 });
}
await bucket.put(destination, src.body, {
httpMetadata: src.httpMetadata,
customMetadata: src.customMetadata,
});
if (destination_exists) {
response = new Response(null, { status: 204 });
return new Response(null, { status: 204 });
} else {
response = new Response('', { status: 201 });
return new Response('', { status: 201 });
}
}
}
break;
case 'MOVE': {
}
async function handle_move(request: Request, bucket: R2Bucket): Promise<Response> {
let resource_path = make_resource_path(request);
let overwrite = request.headers.get('Overwrite') === 'T';
let destination_header = request.headers.get('Destination');
if (destination_header === null) {
response = new Response('Bad Request', { status: 400 });
break;
return new Response('Bad Request', { status: 400 });
}
let destination = new URL(destination_header).pathname.slice(1);
let destination_exists = await bucket.head(destination);
if (destination_exists && !overwrite) {
response = new Response('Precondition Failed', { status: 412 });
break;
return new Response('Precondition Failed', { status: 412 });
}
// TODO delete recursively (if destination is a directory)
@ -619,33 +599,60 @@ export default {
cursor = r2_objects.cursor;
}
} while (r2_objects.truncated)
response = new Response('', { status: 201 });
return new Response('', { status: 201 });
}
break;
default: {
response = new Response('Bad Request', { status: 400 });
return new Response('Bad Request', { status: 400 });
}
}
} else {
let src = await bucket.get(resource_path);
if (src === null) {
response = new Response('Not Found', { status: 404 });
break;
return new Response('Not Found', { status: 404 });
}
await bucket.put(destination, src.body, {
httpMetadata: src.httpMetadata,
customMetadata: src.customMetadata,
});
if (destination_exists) {
response = new Response(null, { status: 204 });
return new Response(null, { status: 204 });
} else {
response = new Response('', { status: 201 });
return new Response('', { status: 201 });
}
}
}
async function dispatch_handler(request: Request, bucket: R2Bucket): Promise<Response> {
switch (request.method) {
case 'OPTIONS': {
return await handle_options(request, bucket);
}
case 'HEAD': {
return await handle_head(request, bucket);
}
case 'GET': {
return await handle_get(request, bucket);
}
case 'PUT': {
return await handle_put(request, bucket);
}
case 'DELETE': {
return await handle_delete(request, bucket);
}
case 'MKCOL': {
return await handle_mkcol(request, bucket);
}
case 'PROPFIND': {
return await handle_propfind(request, bucket);
}
case 'COPY': {
return await handle_copy(request, bucket);
}
case 'MOVE': {
return await handle_move(request, bucket);
}
break;
default: {
response = new Response('Method Not Allowed', {
return new Response('Method Not Allowed', {
status: 405,
headers: {
'Allow': SUPPORT_METHODS.join(', '),
@ -654,15 +661,22 @@ export default {
});
}
}
}
if (request.method === 'HEAD') {
response = new Response(null, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const { bucket } = env;
if (request.headers.get('Authorization') !== `Basic ${btoa(`${env.USERNAME}:${env.PASSWORD}`)}`) {
return new Response('Unauthorized', {
status: 401, headers: {
'WWW-Authenticate': 'Basic realm="webdav"',
}
});
}
let response: Response = await dispatch_handler(request, bucket);
// Set CORS headers
response.headers.set('Access-Control-Allow-Origin', request.headers.get('Origin') ?? '*');
response.headers.set('Access-Control-Allow-Methods', SUPPORT_METHODS.join(', '));