Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 84 additions & 3 deletions functions/webdav/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export async function handleRequestPostCreateMultipart({
});

const { key, uploadId } = multipartUpload;
return new Response(JSON.stringify({ key, uploadId }));
return new Response(JSON.stringify({ key, uploadId }), {
headers: { "Content-Type": "application/json" }
});
}

export async function handleRequestPostCompleteMultipart({
Expand All @@ -26,20 +28,96 @@ export async function handleRequestPostCompleteMultipart({
const url = new URL(request.url);
const uploadId = new URLSearchParams(url.search).get("uploadId");
if (!uploadId) return notFound();

const multipartUpload = bucket.resumeMultipartUpload(path, uploadId);

const completeBody: { parts: Array<any> } = await request.json();

try {
const object = await multipartUpload.complete(completeBody.parts);
return new Response(null, {
headers: { etag: object.httpEtag },
headers: {
etag: object.httpEtag,
"Content-Type": "application/json"
},
});
} catch (error: any) {
return new Response(error.message, { status: 400 });
}
}

// 新增:处理流式分片上传
export async function handleRequestPostStreamChunk({
bucket,
path,
request,
}: RequestHandlerParams) {
const url = new URL(request.url);
const uploadId = url.searchParams.get("uploadId");
const partNumber = url.searchParams.get("partNumber");

if (!uploadId || !partNumber) {
return new Response("Bad Request", { status: 400 });
}

const transferEncoding = request.headers.get("transfer-encoding");

if (transferEncoding === "chunked") {
// 处理流式分片数据
const reader = request.body?.getReader();
if (!reader) {
return new Response("Bad Request", { status: 400 });
}

const chunks: Uint8Array[] = [];

try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}

const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const combined = new Uint8Array(totalLength);
let offset = 0;

for (const chunk of chunks) {
combined.set(chunk, offset);
offset += chunk.length;
}

const multipartUpload = bucket.resumeMultipartUpload(path, uploadId);
const uploadedPart = await multipartUpload.uploadPart(
parseInt(partNumber),
combined
);

return new Response(null, {
headers: {
"Content-Type": "application/json",
etag: uploadedPart.etag
},
});

} catch (error) {
console.error("Stream chunk upload error:", error);
return new Response("Internal Server Error", { status: 500 });
}
}

// 回退到常规分片上传
const multipartUpload = bucket.resumeMultipartUpload(path, uploadId);
const uploadedPart = await multipartUpload.uploadPart(
parseInt(partNumber),
request.body
);

return new Response(null, {
headers: { "Content-Type": "application/json", etag: uploadedPart.etag },
});
}

export const handleRequestPost = async function ({
bucket,
path,
Expand All @@ -53,8 +131,11 @@ export const handleRequestPost = async function ({
}

if (searchParams.has("uploadId")) {
if (searchParams.has("partNumber")) {
return handleRequestPostStreamChunk({ bucket, path, request });
}
return handleRequestPostCompleteMultipart({ bucket, path, request });
}

return new Response("Method not allowed", { status: 405 });
};
};
76 changes: 74 additions & 2 deletions functions/webdav/put.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,65 @@ async function handleRequestPutMultipart({
});
}


async function handleStreamedUpload({
bucket,
path,
request,
}: RequestHandlerParams) {
const contentLength = request.headers.get("content-length");
const transferEncoding = request.headers.get("transfer-encoding");

if (transferEncoding === "chunked" || !contentLength) {
// 处理 chunked transfer encoding
const reader = request.body?.getReader();
if (!reader) {
return new Response("Bad Request", { status: 400 });
}

// 创建可写流来处理分块数据
const chunks: Uint8Array[] = [];

try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}

// 合并所有分块
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const combined = new Uint8Array(totalLength);
let offset = 0;

for (const chunk of chunks) {
combined.set(chunk, offset);
offset += chunk.length;
}

// 上传到 R2
const thumbnail = request.headers.get("fd-thumbnail");
const customMetadata = thumbnail ? { thumbnail } : undefined;

const result = await bucket.put(path, combined, {
onlyIf: request.headers,
httpMetadata: request.headers,
customMetadata,
});

if (!result) return new Response("Preconditions failed", { status: 412 });
return new Response("", { status: 201 });

} catch (error) {
console.error("Stream upload error:", error);
return new Response("Internal Server Error", { status: 500 });
}
}

// 回退到常规上传
return handleRegularUpload({ bucket, path, request });
}

export async function handleRequestPut({
bucket,
path,
Expand All @@ -46,6 +105,20 @@ export async function handleRequestPut({
if (parentDir === null) return new Response("Conflict", { status: 409 });
}

// 检查是否为流式上传
const transferEncoding = request.headers.get("transfer-encoding");
if (transferEncoding === "chunked") {
return handleStreamedUpload({ bucket, path, request });
}

return handleRegularUpload({ bucket, path, request });
}

async function handleRegularUpload({
bucket,
path,
request,
}: RequestHandlerParams) {
const thumbnail = request.headers.get("fd-thumbnail");
const customMetadata = thumbnail ? { thumbnail } : undefined;

Expand All @@ -56,6 +129,5 @@ export async function handleRequestPut({
});

if (!result) return new Response("Preconditions failed", { status: 412 });

return new Response("", { status: 201 });
}
}
Loading