파일을 업로드하는 도중에 HTTP 연결이 끊기면 서버가 크래시되던 버그 수정

This commit is contained in:
static
2025-01-15 06:09:27 +09:00
parent ed4da7b1df
commit 9f9c52ff94
3 changed files with 68 additions and 73 deletions

View File

@@ -115,10 +115,8 @@ export const uploadFile = async (
} catch (e) {
await safeUnlink(path);
if (e instanceof IntegrityError) {
if (e.message === "Inactive MEK version") {
error(400, "Invalid MEK version");
}
if (e instanceof IntegrityError && e.message === "Inactive MEK version") {
error(400, "Invalid MEK version");
}
throw e;
}

View File

@@ -66,9 +66,9 @@
info = getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!);
window.alert("파일이 업로드되었어요.");
})
.catch(() => {
.catch((e: Error) => {
// TODO: FIXME
window.alert("파일 업로드에 실패했어요.");
window.alert(`파일 업로드에 실패했어요.\n${e.message}`);
});
};

View File

@@ -6,53 +6,10 @@ import { fileUploadRequest } from "$lib/server/schemas";
import { uploadFile } from "$lib/server/services/file";
import type { RequestHandler } from "./$types";
const parseFormData = async (contentType: string, body: ReadableStream<Uint8Array>) => {
return new Promise<{ metadata: string; content: Readable }>((resolve, reject) => {
let metadata: string | null = null;
let content: Readable | null = null;
type FileMetadata = Parameters<typeof uploadFile>[0];
const bb = Busboy({ headers: { "content-type": contentType } });
bb.on("field", (fieldname, val) => {
if (fieldname !== "metadata") return reject(new Error("Invalid request body"));
if (metadata || content) return reject(new Error("Invalid request body")); // metadata must be first
metadata = val;
});
bb.on("file", (fieldname, file) => {
if (fieldname !== "content") return reject(new Error("Invalid request body"));
if (!metadata || content) return reject(new Error("Invalid request body")); // metadata must be first
content = file;
resolve({ metadata, content });
});
bb.on("finish", () => reject(new Error("Invalid request body")));
bb.on("error", (e) => reject(e));
body.pipeTo(Writable.toWeb(bb));
});
};
export const POST: RequestHandler = async ({ locals, request }) => {
const { userId } = await authorize(locals, "activeClient");
const contentTypeHeader = request.headers.get("Content-Type");
if (!contentTypeHeader?.startsWith("multipart/form-data") || !request.body) {
error(400, "Invalid request body");
}
let metadata;
let content;
try {
const formData = await parseFormData(contentTypeHeader, request.body);
metadata = formData.metadata;
content = formData.content;
} catch (e) {
if (e instanceof Error && e.message === "Invalid request body") {
error(400, "Invalid request body");
}
throw e;
}
const zodRes = fileUploadRequest.safeParse(JSON.parse(metadata));
const parseFileMetadata = (userId: number, json: string) => {
const zodRes = fileUploadRequest.safeParse(JSON.parse(json));
if (!zodRes.success) error(400, "Invalid request body");
const {
parentId,
@@ -73,25 +30,65 @@ export const POST: RequestHandler = async ({ locals, request }) => {
if ((createdAt && !createdAtIv) || (!createdAt && createdAtIv))
error(400, "Invalid request body");
await uploadFile(
{
userId,
parentId,
mekVersion,
encDek: dek,
dekVersion: new Date(dekVersion),
hskVersion,
contentHmac,
contentType,
encContentIv: contentIv,
encName: name,
encNameIv: nameIv,
encCreatedAt: createdAt ?? null,
encCreatedAtIv: createdAtIv ?? null,
encLastModifiedAt: lastModifiedAt,
encLastModifiedAtIv: lastModifiedAtIv,
},
content,
);
return text("File uploaded", { headers: { "Content-Type": "text/plain" } });
return {
userId,
parentId,
mekVersion,
encDek: dek,
dekVersion: new Date(dekVersion),
hskVersion,
contentHmac,
contentType,
encContentIv: contentIv,
encName: name,
encNameIv: nameIv,
encCreatedAt: createdAt ?? null,
encCreatedAtIv: createdAtIv ?? null,
encLastModifiedAt: lastModifiedAt,
encLastModifiedAtIv: lastModifiedAtIv,
} satisfies FileMetadata;
};
export const POST: RequestHandler = async ({ locals, request }) => {
const { userId } = await authorize(locals, "activeClient");
const contentType = request.headers.get("Content-Type");
if (!contentType?.startsWith("multipart/form-data") || !request.body) {
error(400, "Invalid request body");
}
return new Promise<Response>((resolve, reject) => {
const bb = Busboy({ headers: { "content-type": contentType } });
const handler =
<T extends unknown[]>(f: (...args: T) => Promise<void>) =>
(...args: T) => {
f(...args).catch(reject);
};
let metadata: FileMetadata | null = null;
let content: Readable | null = null;
bb.on(
"field",
handler(async (fieldname, val) => {
if (fieldname !== "metadata") error(400, "Invalid request body");
if (metadata || content) error(400, "Invalid request body");
metadata = parseFileMetadata(userId, val);
}),
);
bb.on(
"file",
handler(async (fieldname, file) => {
if (fieldname !== "content") error(400, "Invalid request body");
if (!metadata || content) error(400, "Invalid request body");
content = file;
await uploadFile(metadata, content);
resolve(text("File uploaded", { headers: { "Content-Type": "text/plain" } }));
}),
);
bb.on("error", (e) => content?.emit("error", e) ?? reject(e));
request.body!.pipeTo(Writable.toWeb(bb)).catch(() => {}); // busboy will handle the error
});
};