01ทำไมการอัปโหลดไฟล์ใหญ่ถึง "พัง" ได้ง่าย
ลองนึกภาพผู้ใช้กำลังอัปโหลดวิดีโอขนาด 1.5 GB ผ่านฟอร์ม HTML ธรรมดา ใช้เวลาไปแล้วกว่าครึ่งชั่วโมง แถบโปรเกรสขยับมาถึง 92% แล้วจู่ ๆ Wi-Fi ก็หลุด หรือเบราว์เซอร์แครช ผลลัพธ์คือไฟล์ที่อัปโหลดไปทั้งหมดสูญเปล่า ผู้ใช้ต้องกดอัปโหลดใหม่ตั้งแต่ไบต์แรก
ปัญหานี้เกิดจากธรรมชาติของการอัปโหลดแบบ multipart/form-data ทั่วไป ซึ่งเซิร์ฟเวอร์มองไฟล์ทั้งก้อนเป็นคำขอเดียว ถ้าคำขอนั้นไม่จบสมบูรณ์ เซิร์ฟเวอร์จะไม่รู้เลยว่าไบต์ไหนมาถึงแล้วบ้าง และต้องทิ้งข้อมูลทั้งหมดไป ยิ่งผู้ใช้อยู่ในพื้นที่ที่สัญญาณอินเทอร์เน็ตไม่เสถียร เช่น ใช้เน็ตมือถือระหว่างเดินทาง ปัญหานี้ก็ยิ่งเกิดถี่ขึ้น
ทางออกที่หลายระบบใหญ่ ๆ อย่าง Vimeo หรือ Google Drive ใช้กันคือการอัปโหลดแบบ resumable นั่นคือแบ่งไฟล์เป็นชิ้นเล็ก ๆ (chunk) ส่งทีละชิ้น แล้วให้เซิร์ฟเวอร์จดจำว่าไบต์ล่าสุดที่ได้รับคือไบต์ที่เท่าไร เมื่อการเชื่อมต่อกลับมา ฝั่งไคลเอนต์ก็แค่ถามเซิร์ฟเวอร์ว่า "ตอนนี้ได้รับไปถึงไหนแล้ว" แล้วส่งต่อจากจุดนั้น ไม่ต้องเริ่มนับหนึ่งใหม่
02รู้จัก tus: มาตรฐานเปิดสำหรับ Resumable Upload
tus เป็นโปรโตคอลแบบเปิดที่ถูกออกแบบมาเฉพาะสำหรับการอัปโหลดไฟล์แบบ resumable บน HTTP จุดเด่นของ tus คือมันกำหนดมาตรฐานชัดเจนว่าไคลเอนต์กับเซิร์ฟเวอร์ควรคุยกันอย่างไร ทำให้นักพัฒนาไม่ต้องคิดค้น protocol ของตัวเองตั้งแต่ศูนย์ และมีไลบรารีรองรับในหลายภาษา ทั้งฝั่ง server (PHP, Node.js, Go) และฝั่ง client (JavaScript, iOS, Android)
สำหรับฝั่ง PHP มีไลบรารีชื่อ ankitpokhrel/tus-php ซึ่งเป็น implementation ของ tus server แบบ framework-agnostic หมายความว่าเอาไปต่อกับ CodeIgniter, Laravel หรือ PHP ล้วน ๆ ก็ได้ทั้งนั้น
03tus ทำงานอย่างไรเบื้องหลัง
หัวใจของ tus คือแนวคิดเรื่อง offset หรือ "ตำแหน่งไบต์ล่าสุดที่อัปโหลดสำเร็จ" ขั้นตอนคร่าว ๆ มีดังนี้
- ไคลเอนต์ส่งคำขอ
POSTไปยังเซิร์ฟเวอร์เพื่อ "จอง" การอัปโหลดใหม่ พร้อมแจ้งขนาดไฟล์ทั้งหมด เซิร์ฟเวอร์จะตอบกลับ URL เฉพาะสำหรับไฟล์นั้น - ไคลเอนต์ทยอยส่งข้อมูลเป็นชิ้น ๆ ด้วยคำขอ
PATCHไปยัง URL นั้น พร้อมระบุ offset ปัจจุบันในเฮดเดอร์ - ถ้าการเชื่อมต่อขาดระหว่างทาง ไคลเอนต์สามารถส่งคำขอ
HEADไปถาม URL เดิมเพื่อเช็กว่าตอนนี้เซิร์ฟเวอร์มีข้อมูลถึงไบต์ไหนแล้ว - จากนั้นไคลเอนต์ส่ง
PATCHต่อจาก offset ที่ได้รับ โดยไม่ต้องส่งข้อมูลเดิมซ้ำเลย
04ติดตั้งและสร้าง Server ด้วย tus-php
เริ่มจากติดตั้งไลบรารีผ่าน Composer
โค้ดด้านล่างคือ tus server แบบมินิมอล ที่รับคำขอทุก method ของโปรโตคอล tus แล้วจัดการให้เองทั้งหมด
เพียงเท่านี้ server.php ตัวนี้ก็พร้อมรับคำขอ OPTIONS, POST, HEAD, PATCH และ DELETE ตามสเปกของ tus แล้ว แต่ในงานจริงเราอยากผูกมันเข้ากับเฟรมเวิร์กที่ใช้อยู่มากกว่า ในส่วนถัดไปจะพาไปดูทั้ง CodeIgniter 4 และ Laravel
05ผูก tus-php เข้ากับ CodeIgniter 4
ใน CI4 เราสามารถสร้าง Controller ขึ้นมาตัวเดียวเพื่อรับทุกคำขอที่เกี่ยวกับการอัปโหลด แล้วส่งต่อให้ tus-php จัดการ จากนั้นแปลง response ของ tus-php กลับมาเป็น response ของ CI4
จากนั้นเปิดทางให้ทุก HTTP method ที่ tus ต้องใช้วิ่งเข้ามาที่ Controller เดียวกัน ใน app/Config/Routes.php
เก็บไฟล์ไว้ใต้ WRITEPATH ตามธรรมเนียมของ CI4 ก็ปลอดภัยกว่า เพราะโฟลเดอร์นี้อยู่นอก document root และคุมสิทธิ์การเข้าถึงได้ง่ายกว่าโฟลเดอร์ใน public/
06ผูก tus-php เข้ากับ Laravel
แนวคิดเดียวกันสามารถนำไปใช้กับ Laravel ได้โดยประกาศ route แบบ closure ที่ดักทุก method และทุก path ย่อยภายใต้ /files
ข้อดีของการใช้ storage_path('app/uploads') คือไฟล์ที่กำลังอัปโหลดอยู่จะไม่ถูกเข้าถึงโดยตรงจากเว็บ ต้องผ่าน Controller หรือ route ที่เรากำหนดเองเท่านั้น ตรงกับแนวทาง security ที่ Laravel แนะนำสำหรับไฟล์ที่ยังไม่พร้อมเผยแพร่
07ฝั่ง Client: อัปโหลดแบบ Resumable ด้วย tus-js-client
ฝั่งหน้าเว็บ เราไม่จำเป็นต้องเขียน logic เรื่อง offset หรือ retry เองเลย เพราะมีไลบรารี tus-js-client ที่คุยกับ tus server ตามสเปกให้ครบแล้ว สิ่งที่เราต้องทำคือสร้าง instance ของ tus.Upload แล้วกำหนด endpoint กับ callback ต่าง ๆ
เมธอด findPreviousUploads() คือกุญแจสำคัญของ "resume" ฝั่งไคลเอนต์ มันจะเช็ก localStorage ของเบราว์เซอร์ว่าไฟล์นี้เคยเริ่มอัปโหลดไว้หรือยัง ถ้าเจอก็เรียก resumeFromPreviousUpload() เพื่อสานต่อจากจุดเดิมทันที โดยที่ผู้ใช้ไม่ต้องทำอะไรเพิ่มเลย แม้จะปิดแท็บแล้วเปิดใหม่ก็ตาม
08REST Endpoint ของ tus Server
เพื่อให้เห็นภาพว่าแต่ละคำขอ HTTP ทำหน้าที่อะไรบ้างในวงจรของ tus สรุปไว้ในตารางนี้
09ข้อดี ข้อจำกัด และเมื่อไหร่ควรใช้
tus เหมาะมากกับงานที่ผู้ใช้ต้องอัปโหลดไฟล์ขนาดใหญ่ เช่น ระบบอัปโหลดวิดีโอ ไฟล์ backup หรือไฟล์ CAD แต่ก็ไม่ใช่ทุกโปรเจกต์ที่จำเป็นต้องใช้ ลองดูข้อพิจารณาเหล่านี้ก่อนตัดสินใจ
- เหมาะ: ไฟล์ขนาดใหญ่กว่า 50-100 MB ขึ้นไป หรือกลุ่มผู้ใช้ที่เน็ตไม่เสถียร เช่น ใช้งานผ่านมือถือนอกสถานที่
- เหมาะ: ระบบที่ต้องการให้ผู้ใช้สลับอุปกรณ์ได้ เช่น เริ่มอัปโหลดจากโน้ตบุ๊ก แล้วไปต่อจากมือถือ
- ไม่จำเป็น: ฟอร์มอัปโหลดรูปโปรไฟล์หรือเอกสารขนาดไม่กี่ MB การใช้
multipart/form-dataแบบปกติยังคุ้มค่ากว่าในแง่ความซับซ้อน - ข้อควรระวัง: ต้องวางแผนเรื่อง storage สำหรับ chunk ที่ค้างไว้ และตั้งกลไกลบไฟล์ที่ไม่มีใครมา resume ต่อเป็นเวลานาน เพื่อไม่ให้ดิสก์เต็ม
10สรุป
การอัปโหลดไฟล์ใหญ่ไม่จำเป็นต้องเป็นฝันร้ายของผู้ใช้อีกต่อไป ด้วย tus protocol และไลบรารี tus-php เราสามารถเปลี่ยนการอัปโหลดแบบ "พังแล้วเริ่มใหม่ทั้งหมด" ให้กลายเป็น "หยุดตรงไหน ก็ต่อจากตรงนั้น" ได้ โดยที่ไม่ต้องเขียนกลไก chunk และ offset เองตั้งแต่ศูนย์ ไม่ว่าจะใช้ CodeIgniter 4 หรือ Laravel แนวคิดก็เหมือนกันคือสร้าง endpoint เดียวให้รับทุก method ของ tus แล้วปล่อยให้ไลบรารีจัดการส่วนที่เหลือ
สำหรับใครที่กำลังจะเริ่มฟีเจอร์อัปโหลดไฟล์ขนาดใหญ่ในโปรเจกต์ถัดไป ลองเริ่มจากการรันตัวอย่าง server แบบ minimal ในบทความนี้ดูก่อน แล้วค่อยปรับให้เข้ากับ authentication และ business logic ของระบบจริง
