ในโลกของการพัฒนาเว็บสมัยใหม่ ผู้ใช้งานจำนวนมากต้องมีการอัปโหลดไฟล์ที่มีขนาดใหญ่ เช่น วิดีโอ, ชุดข้อมูล (Datasets), หรือเอกสาร PDF ขนาดมหาศาล อย่างไรก็ตาม PHP และ HTTP มีข้อจำกัดที่สำคัญในการจัดการการอัปโหลดไฟล์ข้ามครั้งหรือไฟล์ที่มีขนาดเกินกว่าหน่วยความจำ (memory limit) หรือเวลาประมวลผล (execution time limit) ของเซิร์ฟเวอร์ การพยายามส่งไฟล์เหล่านี้ด้วยวิธีปกติจึงมักล้มเหลว
Chunked Upload คืออะไร?
Chunked Upload คือเทคนิคการแบ่งไฟล์ขนาดใหญ่ออกเป็นส่วนย่อย ๆ (Chunks) ที่มีขนาดคงที่ ก่อนที่จะส่งข้อมูลเหล่านี้ไปยังเซิร์ฟเวอร์ผ่าน HTTP อย่างต่อเนื่อง เมื่อทุก Chunk ถูกส่งมาถึงฝั่ง Server แล้ว PHP จะทำหน้าที่รวบรวมและประกอบชิ้นส่วนเหล่านั้นให้กลับมาเป็นไฟล์ต้นฉบับที่สมบูรณ์
หลักการทำงานโดยย่อ
- Client-Side (JavaScript): ฝั่งไคลเอนต์จะรับผิดชอบในการแบ่งไฟล์ออกเป็นส่วน ๆ และส่งแต่ละส่วนไปยัง API endpoint ของ PHP
- Server-Side (PHP): Script ใน PHP จะต้องทำหน้าที่พิเศษ คือการรับและบันทึก Chunk เข้าไปยังไฟล์ชั่วคราว (Temporary Location) โดยต้องทราบว่า Chunk นี้คือลำดับที่เท่าไหร่ และมีขนาดเท่าใด เพื่อให้สามารถเรียงร้อยกลับมาได้
- Completion: เมื่อได้รับ Chunk ลำดับสุดท้ายแล้ว PHP จะทำการยืนยันความสมบูรณ์ของไฟล์และรวมส่วนประกอบทั้งหมดเป็นไฟล์ปลายทางจริง พร้อมทั้งลบไฟล์ชั่วคราวก่อนหน้าออก
ทำไมจึงสำคัญ? การใช้ Chunked Upload ทำให้เราสามารถจัดการกับไฟล์ที่มีขนาดหลายร้อยเมกะไบต์หรือกิกะไบต์ได้ โดยไม่ติดขัดข้อจำกัดของ upload_max_filesize หรือ max_execution_time ของ PHP ธรรมดา
ตัวอย่างการใช้งานและรูปแบบโค้ด (Code Examples)
ในการทำ Chunked Upload จริง ๆ ส่วนใหญ่แล้ว จะต้องใช้ JavaScript ในการแบ่งไฟล์จากฝั่ง Client แต่เราจะเน้นที่ส่วนของ PHP Backend ซึ่งเป็นหัวใจของการรับและประกอบชิ้นส่วน
โครงสร้าง PHP Backend (upload_handler.php)
file_id: ID ของไฟล์ต้นฉบับchunk_index: ลำดับของ Chunk (เช่น 0, 1, 2…)total_chunks: จำนวน Chunk ทั้งหมดที่คาดหวังchunk_data: ข้อมูลไบนารีของ Chunk นั้น ๆ (รับผ่าน Body Request)
'error', 'message' => 'Missing required chunk data parameters.']);
exit;
}
// 1. สร้างชื่อไฟล์ชั่วคราวที่ระบุ Chunk และ File ID
$tempFileName = $fileId . "_chunk_" . $chunkIndex . ".tmp";
$fullTempPath = TEMP_DIR . $tempFileName;
// ตรวจสอบและสร้าง Directory ชั่วคราวถ้ายังไม่มี
if (!is_dir(TEMP_DIR)) {
mkdir(TEMP_DIR, 0777, true);
}
// 2. บันทึก Chunk เข้าสู่ไฟล์ชั่วคราว
if (file_put_contents($fullTempPath, $chunkData) === false) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => 'Failed to write chunk data.']);
exit;
}
// 3. การตรวจสอบและประกอบไฟล์ (Logic นี้ควรทำเมื่อได้รับ Chunk ลำดับสุดท้ายเท่านั้น)
if ($chunkIndex == $totalChunks - 1) {
$finalFilePath = 'uploads/' . basename($fileId); // ไฟล์ปลายทางจริง
$tempFiles = glob(TEMP_DIR . $fileId . "_chunk_*.tmp");
// ตรวจสอบความสมบูรณ์ของจำนวน Chunk
if (count($tempFiles) !== $totalChunks) {
http_response_code(422);
echo json_encode(['status' => 'error', 'message' => 'Incomplete upload. Expected ' . $totalChunks . ' chunks, found ' . count($tempFiles) . '.']);
} else {
// เรียงร้อย Chunk ทั้งหมดเข้าด้วยกัน (The Core Logic)
$sourcePaths = array_map(function($path, $index) { return $path; }, $tempFiles, range(0, $totalChunks - 1));
$handle = fopen($finalFilePath, 'wb');
foreach ($sourcePaths as $chunkPath) {
$sourceHandle = fopen($chunkPath, 'rb');
if ($sourceHandle) {
while (!feof($sourceHandle)) {
fwrite($handle, fread($sourceHandle, 8192)); // อ่านและเขียนทีละบล็อกเพื่อประสิทธิภาพ
}
fclose($sourceHandle);
}
}
fclose($handle);
// 4. การล้างไฟล์ชั่วคราว (Crucial Step)
foreach ($tempFiles as $path) {
unlink($path);
}
http_response_code(200);
echo json_encode(['status' => 'success', 'message' => 'File assembled successfully.', 'file_path' => $finalFilePath]);
}
} else {
// กรณีที่ยังไม่ถึง Chunk สุดท้าย ให้ตอบกลับว่ารับข้อมูลเรียบร้อยแล้ว
http_response_code(202); // 202 Accepted: รับทราบและกำลังดำเนินการต่อ
echo json_encode(['status' => 'processing', 'message' => 'Chunk received and saved. Waiting for final chunk...']);
}
?>
คำอธิบายโค้ดที่สำคัญ
file_get_contents('php://input'): เป็นส่วนสำคัญที่สุดในการรับข้อมูลไบนารี (Binary Data) จาก Body ของ HTTP Request เนื่องจากไฟล์ไม่ได้ถูกส่งผ่านตัวแปร Superglobal อย่าง$_FILESแต่ถูกส่งมาเป็น Stream ข้อมูลดิบ- Directory Structure
temp_uploads/: ต้องสร้างพื้นที่จัดเก็บชั่วคราว แยกจากไฟล์จริง เพื่อหลีกเลี่ยงการเขียนทับและการเข้าถึงที่ผิดพลาด และทำให้กระบวนการ Cleanup ง่ายขึ้น - Assembly Logic (The Core): การใช้
fopen,freadและfwriteในลูป เป็นวิธีที่มีประสิทธิภาพสูงในการจัดการกับข้อมูลไบนารีขนาดใหญ่ โดยหลีกเลี่ยงการสร้าง String ขนาดมหึมาในหน่วยความจำ - Security: การล้างไฟล์
unlink(): หลังจากประกอบไฟล์เสร็จสิ้น สิ่งสำคัญที่สุดคือต้องทำความสะอาด (Clean up) โดยการลบ Chunk ชิ้นส่วนที่อยู่ในไดเร็กทอรี่ชั่วคราวทั้งหมด เพื่อป้องกันข้อมูลส่วนตัวตกค้างและปัญหาพื้นที่จัดเก็บ
ข้อควรระวัง Security และ Best Practices
- ⚠️ การจัดการทรัพยากร (Resource Management): หาก Client หลุดก่อนส่ง Chunk สุดท้าย PHP จะมีไฟล์ชั่วคราวจำนวนมากหลงเหลืออยู่ แนวทางแก้ไขที่ดีที่สุดคือการใช้ Cron Job หรือ Cleanup Script ที่ทำงานเป็นระยะเพื่อลบ Directory
temp_uploadsที่ไม่มีการเข้าใช้งานเกินกว่าช่วงเวลาที่กำหนด - 🔒 การป้องกัน Path Traversal และ ID Spoofing: ห้ามเชื่อถือค่า
fileIdที่มาจาก Client โดยเด็ดขาด ควรใช้ Unique Identifier (UUID) หรือการ Hash ค่าที่ได้รับมาเพื่อสร้างชื่อไฟล์ชั่วคราว เพื่อให้แน่ใจว่าชื่อไฟล์นั้นไม่สามารถนำไปชี้ไปยัง Directory อื่นในระบบได้
// แนวทางป้องกันที่ดีที่สุดคือการใช้ Hash หรือ UUID ที่สร้างจาก Server Side
$safeFileId = hash('sha256', $fileId);
202 Accepted เมื่อรับ Chunk และยังไม่เสร็จสมบูรณ์, ใช้ 422 Unprocessable Entity เมื่อข้อมูลที่ได้รับมามีโครงสร้างผิดพลาด (เช่น ลำดับ Chunk ผิด)สรุปและการนำไปประยุกต์ใช้งาน
Chunked Upload เป็นเครื่องมือที่ทรงพลังและจำเป็นสำหรับการสร้างระบบอัปโหลดไฟล์ขนาดใหญ่ในระดับองค์กร (Enterprise-level applications) มันช่วยให้ Backend PHP ของเรามีความยืดหยุ่นในการจัดการกับข้อจำกัดของเซิร์ฟเวอร์และเครือข่ายได้อย่างมีประสิทธิภาพ
- Client (JS): รับผิดชอบการแบ่งไฟล์และการจัดการสถานะการส่ง
- Server (PHP): ทำหน้าที่เป็นตัวรับข้อมูลชั่วคราว (Temporary Receiver) อย่างปลอดภัย และมี Logic ในการประกอบและล้างข้อมูลอย่างสมบูรณ์เมื่อได้รับ Chunk ลำดับสุดท้าย
การทำความเข้าใจหลักการเหล่านี้ ไม่เพียงแต่จะแก้ปัญหาทางเทคนิคด้านขนาดไฟล์เท่านั้น แต่ยังเป็นการยกระดับความเสถียร ความปลอดภัย และสถาปัตยกรรมของ Backend System ของคุณให้ทัดเทียมกับระบบระดับโลกอีกด้วย
อ่านเพิ่มเติม