Multipart Upload เป็นกลไก HTTP ที่อนุญาตให้ถ่ายโอนข้อมูลหลายส่วนที่แตกต่างกันในคำขอเดียว รวมถึงฟิลด์ข้อความและไฟล์ไบนารี แต่ละส่วนจะถูกแยกด้วยสตริงขอบเขตที่ไม่ซ้ำกันและมีส่วนหัว Content-Type ของตัวเอง ตาม MDN Web Docs, 2025 multipart/form-data เป็นรูปแบบมาตรฐานสำหรับการอัปโหลดไฟล์ผ่านฟอร์ม HTML และใช้กันอย่างแพร่หลายในเว็บและแอปพลิเคชันมือถือสำหรับส่งรูปภาพ เอกสาร และไฟล์อื่นๆ ไปยังเซิร์ฟเวอร์
ประเด็นสำคัญ
Multipart Upload เป็นวิธีการถ่ายโอนข้อมูลผ่านโปรโตคอล HTTP ซึ่งเนื้อหาของคำขอประกอบด้วยหลายส่วนที่แยกจากกันอย่างมีเหตุผล แต่ละส่วนสามารถมีข้อมูลประเภทต่างกัน: ฟิลด์ฟอร์มข้อความ ไฟล์ไบนารี วัตถุ JSON หรือรูปภาพ ทุกส่วนถูกบรรจุในคำขอ POST เดียว ซึ่งช่วยลดความจำเป็นในการส่ง N การเรียก HTTP แยกกัน Multipart Upload เป็นส่วนสำคัญของฟอร์มเว็บและ API การอัปโหลดไฟล์
รูปแบบ multipart ถูกกำหนดในข้อกำหนด RFC 2046 ซึ่งเป็นส่วนหนึ่งของมาตรฐาน MIME สำหรับข้อความอีเมล และต่อมาถูกปรับใช้สำหรับ HTTP ใน RFC 1867 ปัจจุบัน การพัฒนาเว็บใช้เกือบเฉพาะ multipart/form-data ซึ่งเป็นหนึ่งในประเภทย่อยของ multipart ที่ออกแบบมาสำหรับฟอร์มที่มีไฟล์ ประเภทย่อยอื่นๆ — multipart/mixed (สำหรับไฟล์แนบตามอำเภอใจ) และ multipart/byteranges (สำหรับการดาวน์โหลดไฟล์บางส่วน) — ถูกใช้น้อยกว่ามาก
ความแตกต่างพื้นฐานระหว่าง multipart และ application/x-www-form-urlencoded ทั่วไปคือ อย่างหลังจะเข้ารหัสข้อมูลทั้งหมดเป็นสตริงที่เข้ากันได้กับ URI และไม่รองรับไฟล์ไบนารี ในทางกลับกัน Multipart/form-data จะส่งแต่ละไฟล์ในรูปแบบไบนารีดั้งเดิมโดยไม่ต้องเข้ารหัส ซึ่งมีประสิทธิภาพมากกว่าและไม่สูญเสียความแม่นยำ ขนาดของคำขอ ด้วย multipart จะมากกว่าผลรวมของขนาดไฟล์ 5-15% เสมอ เนื่องจากค่าใช้จ่ายเพิ่มเติมของส่วนหัวของส่วนและขอบเขต
Multipart Upload ถูกใช้ทุกที่ที่ต้องการอัปโหลดไฟล์: รูปโปรไฟล์และรูปประจำตัวในโซเชียลเน็ตเวิร์ก ไฟล์แนบในแอปส่งข้อความ เอกสารในระบบ CRM รูปภาพสินค้าในร้านค้าออนไลน์ ในแอปพลิเคชันมือถือ Multipart Upload ใช้สำหรับส่งไฟล์มีเดียไปยังเซิร์ฟเวอร์ — รูปภาพจากกล้องอุปกรณ์ บันทึกเสียง คลิปวิดีโอ ตาม Cloudflare Research ประมาณ 15% ของคำขอ POST ทั้งหมดบนเว็บใช้ multipart/form-data
Multipart Upload และ Chunked Transfer เป็นกลไกที่แตกต่างกัน Multipart แบ่งคำขอออกเป็นส่วนที่มีความหมาย (ฟิลด์และไฟล์) ในขณะที่ Chunked Transfer แบ่งสตรีมข้อมูลเป็นส่วนย่อยสำหรับการส่งโดยไม่ทราบขนาดทั้งหมด Multipart สามารถส่งภายใน Chunked Transfer: เซิร์ฟเวอร์ส่งการตอบสนองแบบ multipart เป็นส่วนๆ โดยไม่ทราบขนาดที่สมบูรณ์ กลไกเหล่านี้ไม่ขัดแย้งกันและแก้ปัญหาที่แตกต่างกันในระดับที่แตกต่างกัน
เมื่อเบราว์เซอร์ส่งฟอร์มที่มีแอตทริบิวต์ enctype="multipart/form-data" มันจะสร้างเนื้อหาของคำขอในรูปแบบ multipart แต่ละฟิลด์ฟอร์มกลายเป็นบล็อกแยกกัน โดยแยกจากบล็อกอื่นด้วยสตริงขอบเขต (boundary) ขอบเขตถูกสร้างขึ้นโดยอัตโนมัติและเป็นลำดับอักขระที่ไม่ซ้ำกันซึ่งรับประกันว่าจะไม่ปรากฏภายในข้อมูล ไคลเอ็นต์ เพิ่มขอบเขตนี้ลงในส่วนหัว Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryX7K
แต่ละบล็อกเริ่มต้นด้วย --boundary และมีส่วนหัว Content-Disposition พร้อมชื่อฟิลด์ (name) และสำหรับไฟล์ ชื่อไฟล์ต้นฉบับ (filename) หลังจากบรรทัดว่างจะตามด้วยข้อมูลฟิลด์จริงหรือเนื้อหาไฟล์ในรูปแบบไบนารี คำขอสิ้นสุดด้วยสตริง --boundary-- เซิร์ฟเวอร์ แยกวิเคราะห์สตรีมที่ได้รับ: ขั้นแรกหาขอบเขต จากนั้นแยกส่วนหัวของแต่ละส่วน กำหนดชนิดข้อมูล และส่งต่อไปยังตัวจัดการฟอร์มหรือตัวควบคุม API
ตาม IETF RFC 7578 multipart/form-data ไม่ต้องการระบุ charset สำหรับแต่ละส่วน เนื่องจากฟิลด์ข้อความถือว่าเป็น UTF-8 และส่วนไบนารีมีไฟล์ในการเข้ารหัสต้นฉบับ ขนาดของส่วนหนึ่งไม่ถูกจำกัดโดยโปรโตคอล — ขีดจำกัดถูกกำหนดที่ระดับเซิร์ฟเวอร์: ตัวอย่างเช่น ใน Nginx ผ่าน client_max_body_size ใน Spring Boot ผ่าน spring.servlet.multipart.max-file-size
Boundary เป็นสตริงที่ไม่ซ้ำกันซึ่งไม่ควรปรากฏในข้อมูลที่ส่ง โดยปกติจะเริ่มต้นด้วยคำนำหน้า (เช่น ----WebKitFormBoundary หรือ ----Boundary) และมีอักขระแบบสุ่ม เบราว์เซอร์และไคลเอ็นต์ HTTP สร้าง boundary โดยอัตโนมัติ ความยาวของ boundary ต้องไม่เกิน 70 อักขระตาม RFC 2046 แต่ละส่วนถูกแยกด้วยสตริง --boundary\r\n และจุดสิ้นสุดของคำขอถูกทำเครื่องหมายด้วย --boundary--\r\n
คำขอแบบ multipart มีโครงสร้างที่เข้มงวดซึ่งกำหนดโดยมาตรฐาน MIME และ HTTP ส่วนหัวของคำขอตั้งค่า Content-Type: multipart/form-data พร้อมพารามิเตอร์ boundary เนื้อหาของคำขอประกอบด้วยลำดับของส่วน แต่ละส่วนมีส่วนหัวและเนื้อหาของตัวเอง ส่วนหัวของส่วน รวมถึง Content-Disposition (บังคับ) และ Content-Type (ไม่บังคับ — สำหรับไฟล์) บรรทัดว่างระหว่างส่วนหัวของส่วนและข้อมูลเป็นสิ่งจำเป็น
| องค์ประกอบ | ตัวอย่าง | ความจำเป็น |
|---|---|---|
| Content-Type | multipart/form-data; boundary=---Bnd123 | ใช่ |
| ตัวแบ่งส่วน | ---Bnd123 | ใช่ (ก่อนแต่ละส่วน) |
| Content-Disposition | form-data; name="avatar"; filename="photo.jpg" | ใช่ |
| Content-Type ของส่วน | image/jpeg | สำหรับไฟล์ |
| เนื้อหาของส่วน | [ข้อมูลรูปภาพไบนารี] | ใช่ |
| ขอบเขตสิ้นสุด | ---Bnd123-- | ใช่ (สิ้นสุดคำขอ) |
ลองพิจารณาตัวอย่างจริงของคำขอแบบ multipart ที่ส่งฟิลด์ข้อความและไฟล์รูปภาพ ไคลเอ็นต์ สร้างส่วนหัว Content-Type ด้วย boundary ที่ไม่ซ้ำกัน เนื้อหาของคำขอประกอบด้วยฟิลด์ฟอร์มทั้งหมดตามลำดับ เมื่อได้รับ เซิร์ฟเวอร์จะแยกวิเคราะห์ส่วนเหล่านี้และให้นักพัฒนาเข้าถึงแต่ละฟิลด์เป็นวัตถุแยกกัน วิธีการนี้ช่วยให้ประมวลผลฟอร์มที่ซับซ้อนพร้อมไฟล์ในการเรียก HTTP เดียว
import okhttp3.*
import java.io.File
fun uploadFile() {
val client = OkHttpClient()
val imageFile = File("/path/to/photo.jpg")
val requestBody = MultipartBody.Builder()
.setType(MediaType.parse("multipart/form-data"))
.addFormDataPart("username", "john_doe")
.addFormDataPart(
"avatar", "photo.jpg",
RequestBody.create(
MediaType.parse("image/jpeg"), imageFile
)
)
.build()
val request = Request.Builder()
.url("https://api.example.com/upload")
.post(requestBody)
.build()
client.newCall(request).execute().use { response ->
println("อัปโหลดแล้ว: ${response.isSuccessful}")
}
}
ฝั่งเซิร์ฟเวอร์ คำขอแบบ multipart จะถูกแยกวิเคราะห์โดยเฟรมเวิร์กหรือด้วยตนเอง ใน Spring Boot คำอธิบายประกอบ @RequestParam("avatar") MultipartFile file ก็เพียงพอแล้ว และเฟรมเวิร์กจะแยกไฟล์จากคำขอ multipart โดยอัตโนมัติ ใน Ktor บน Kotlin ใช้ receiveMultipart() ใน Express.js — มิดเดิลแวร์ multer เซิร์ฟเวอร์สามารถเข้าถึงแต่ละฟิลด์ฟอร์มและแต่ละไฟล์ที่อัปโหลดได้อย่างอิสระ บันทึกไฟล์ลงดิสก์หรือพื้นที่จัดเก็บคลาวด์ และส่งคืน URL หรือตัวระบุให้กับไคลเอ็นต์
Multipart Upload มีข้อดีสำคัญหลายประการเหนือวิธีการถ่ายโอนข้อมูลอื่นๆ หนึ่งคำขอแทนที่จะหลายคำขอ — ฟิลด์ฟอร์มและไฟล์ทั้งหมดถูกส่งในการเรียก HTTP เดียว ซึ่งลดภาระบนเครือข่ายและเซิร์ฟเวอร์ ไม่จำเป็นต้องเปิด N การเชื่อมต่อเพื่ออัปโหลด N ไฟล์ — ทุกอย่างถูกบรรจุใน POST เดียว ซึ่งสำคัญโดยเฉพาะสำหรับแอปพลิเคชันมือถือ ที่การเชื่อมต่อ HTTP แต่ละครั้งหมายถึงความหน่วงและการสิ้นเปลืองแบตเตอรี่
การส่งแบบไบนารีโดยไม่ต้องเข้ารหัส — แตกต่างจาก application/x-www-form-urlencoded ที่ข้อมูลไบนารีถูกเข้ารหัส base64 (เพิ่มขนาด 33%) multipart/form-data ส่งไฟล์ในรูปแบบไบนารีดั้งเดิม ซึ่งมีประสิทธิภาพมากกว่าทั้งขนาดและความเร็ว สำหรับไฟล์ขนาดใหญ่เกิน 10 MB ความแตกต่างจะสำคัญ: คำขอ multipart จะเล็กกว่าคำขอที่เข้ารหัส URL ที่มีไฟล์เดียวกันถึง 30%
โครงสร้างที่ยืดหยุ่น — multipart อนุญาตให้รวมฟิลด์ประเภทต่างๆ ในลำดับใดก็ได้ ฟอร์มสามารถมีฟิลด์ข้อความ หลายไฟล์ ข้อมูล JSON และฟิลด์ที่ซ่อนพร้อมกัน แต่ละส่วน มี Content-Type ของตัวเอง ซึ่งอนุญาตให้ผสมข้อมูลข้อความและไบนารี สำหรับการเปรียบเทียบ: การเข้ารหัส base64 เพิ่มขนาด 33% ในขณะที่ multipart เพิ่มเพียงประมาณ 5-15% สำหรับส่วนหัวบริการ
ตามการวิจัยของ HTTP Archive, 2025 multipart/form-data ถูกใช้ใน 94% ของกรณีอัปโหลดไฟล์บนเว็บ ทางเลือกอื่น — base64 ใน JSON (4%) และการส่งโดยตรงผ่าน WebSocket (2%) JSON กับ base64 สะดวกสำหรับ API ที่ข้อมูลอื่นๆ ทั้งหมดก็อยู่ใน JSON เช่นกัน แต่ไม่มีประสิทธิภาพสำหรับไฟล์ขนาดใหญ่ WebSocket เหมาะสำหรับข้อมูลเรียลไทม์ แต่ไม่ได้รับการสนับสนุนจากโครงสร้างพื้นฐาน HTTP ทั้งหมด Multipart ยังคงเป็นมาตรฐานสำหรับการอัปโหลดไฟล์เนื่องจากความเรียบง่ายและประสิทธิภาพ
ในแอปพลิเคชันมือถือ Multipart Upload ใช้สำหรับส่งเนื้อหามีเดียจากอุปกรณ์ผู้ใช้: รูปภาพจากแกลเลอรี ภาพถ่ายจากกล้อง บันทึกเสียง ไฟล์เอกสาร บน Android วิธีการมาตรฐานคือ OkHttp กับ MultipartBody.Builder ซึ่งช่วยให้สร้างคำขอ multipart ได้ง่าย Retrofit ก็รองรับ multipart ผ่านคำอธิบายประกอบ @Multipart และ @Part นักพัฒนาระบุชนิดข้อมูลสำหรับแต่ละส่วน และไคลเอ็นต์ HTTP จะสร้างส่วนหัวที่ถูกต้องโดยอัตโนมัติ
บน iOS งานเดียวกันนี้แก้ไขด้วย URLSession พร้อม HTTPBodyStream ที่กำหนดเองหรือผ่าน Alamofire กับ multipartFormData Alamofire มีเมธอด upload(multipartFormData:) ที่สะดวกสำหรับการส่งคำขอ multipart บนทั้งสองแพลตฟอร์ม ต้องพิจารณาขนาดของไฟล์ที่อัปโหลด — สำหรับไฟล์ขนาดใหญ่ (เกิน 10-20 MB) แนะนำให้ใช้การอัปโหลดพื้นหลังเพื่อให้แอปพลิเคชันไม่สิ้นสุดเมื่อย่อเล็กสุด บน Android ทำได้ผ่าน DownloadManager หรือ WorkManager บน iOS — ผ่าน URLSession พร้อมการกำหนดค่าพื้นหลัง
เมื่ออัปโหลดไฟล์ในแอปพลิเคชันมือถือ ต้องพิจารณาสถานะเครือข่าย Connectivity Manager บน Android ช่วยตรวจสอบว่า Wi-Fi หรือข้อมูลมือถือพร้อมใช้งานและเลือกเวลาที่เหมาะสมที่สุดสำหรับการอัปโหลด สำหรับไฟล์ขนาดใหญ่ เช่น วิดีโอ แนะนำให้เลื่อนการอัปโหลดจนกว่าจะเชื่อมต่อ Wi-Fi เพื่อไม่ให้ใช้ข้อมูลมือถือของผู้ใช้ WorkManager บน Android อนุญาตให้ตั้งค่าข้อจำกัดดังกล่าวผ่าน NetworkType.UNMETERED
ก่อนส่งไฟล์ผ่าน Multipart Upload แอปพลิเคชันมือถือมักจะบีบอัดและปรับขนาดรูปภาพ การบีบอัด JPEG ด้วยคุณภาพ 85% ลดขนาดไฟล์ 3-5 เท่าโดยไม่สูญเสียคุณภาพที่สังเกตได้สำหรับการดูบนหน้าจอ การปรับขนาดรูปภาพเป็น 1920px ที่ด้านยาวยิ่งลดขนาดลง บน Android ใช้ Bitmap.compress() บน iOS — UIImageJPEGRepresentation ด้วยพารามิเตอร์การบีบอัด 0.85 การเพิ่มประสิทธิภาพดังกล่าวช่วยเร่งการอัปโหลดและประหยัดข้อมูลมือถือ
ข้อผิดพลาดที่พบบ่อยที่สุดใน Multipart Upload คือการเกินขีดจำกัดขนาดคำขอบนเซิร์ฟเวอร์ โดยค่าเริ่มต้น Nginx จำกัดขนาดเนื้อหาคำขอไว้ที่ 1 MB (client_max_body_size) และ Tomcat จำกัดไว้ที่ 2 MB (maxSwallowSize) หากนักพัฒนาไม่เพิ่มขีดจำกัดเหล่านี้ เซิร์ฟเวอร์จะส่งคืนข้อผิดพลาด 413 Request Entity Too Large วิธีแก้ไขคือกำหนดขนาดอัปโหลดสูงสุดบนเซิร์ฟเวอร์อย่างชัดเจนและแสดงคำเตือนบนไคลเอ็นต์หากไฟล์เกินขนาดที่อนุญาต
ปัญหาที่สองคือการจัดการคำขอ multipart ที่ไม่ถูกต้องระหว่างการสตรีมเนื้อหา เซิร์ฟเวอร์บางตัวพยายามโหลดคำขอ multipart ทั้งหมดลงในหน่วยความจำก่อนแยกวิเคราะห์ ซึ่งนำไปสู่ OutOfMemoryError สำหรับไฟล์ขนาดใหญ่ เซิร์ฟเวอร์สมัยใหม่ (Nginx, Spring Boot, Ktor) รองรับการแยกวิเคราะห์แบบสตรีมมิ่ง multipart ซึ่งแต่ละส่วนจะถูกประมวลผลเมื่อมาถึง นักพัฒนาต้องตรวจสอบให้แน่ใจว่าเซิร์ฟเวอร์ถูกกำหนดค่าสำหรับการประมวลผลแบบสตรีมมิ่งของคำขอ multipart
ปัญหาประเภทที่สามคือ การหมดเวล当她อัปโหลดไฟล์ขนาดใหญ่ ไคลเอ็นต์ HTTP มีการตั้งค่า readTimeout และ connectTimeout ที่อาจทำงานระหว่างการอัปโหลดไฟล์ขนาดใหญ่เกิน 50-100 MB เป็นเวลานาน วิธีแก้ไขคือเพิ่มการหมดเวลาสำหรับ endpoints การอัปโหลดหรือใช้ chunked transfer encoding ภายใน multipart บนอุปกรณ์มือถือ ยังสำคัญที่จะจัดการกับการขัดจังหวะการอัปโหลดและดำเนินการ resume เมื่อสูญเสียการเชื่อมต่อ
การอัปโหลดไฟล์ ผ่าน multipart เป็นหนึ่งใน endpoints ที่เปราะบางที่สุดของแอปพลิเคชันเว็บ ผู้โจมตีสามารถอัปโหลดสคริปต์ที่เรียกทำงานได้โดยเปลี่ยนชื่อเป็น image.jpg เซิร์ฟเวอร์ต้องตรวจสอบชนิด MIME ของไฟล์ที่อัปโหลดไม่ใช่โดยนามสกุล แต่โดยเนื้อหา (magic bytes) จำกัดชนิดที่อนุญาต และสแกนไฟล์ด้วยโปรแกรมป้องกันไวรัส แนะนำให้เก็บไฟล์ที่อัปโหลดไว้นอก document-root ของเว็บเซิร์ฟเวอร์และให้บริการผ่านตัวควบคุมแยกต่างหากพร้อมตรวจสอบสิทธิ์การเข้าถึง
คำถามที่พบบ่อย
multipart/form-data ส่งแต่ละฟิลด์ฟอร์มเป็นบล็อกแยกต่างหากพร้อมส่วนหัวของตัวเองและรองรับไฟล์ไบนารีโดยไม่ต้องเข้ารหัส application/x-www-form-urlencoded เข้ารหัสข้อมูลทั้งหมดเป็นสตริงที่เข้ากันได้กับ URI (คีย์=ค่า&คีย์2=ค่า2) และไม่รองรับไฟล์โดยตรง — ต้องเข้ารหัส base64
โปรโตคอล HTTP ไม่จำกัดขนาดของคำขอ multipart แต่ในทางปฏิบัติ ขีดจำกัดถูกกำหนดโดยเซิร์ฟเวอร์ Nginx โดยค่าเริ่มต้นจำกัดที่ 1 MB, Apache ที่ 2 MB, Spring Boot ที่ 1 MB หากต้องการอัปโหลดไฟล์ขนาดใหญ่ ให้กำหนดค่า client_max_body_size (Nginx) หรือ spring.servlet.multipart.max-file-size (Spring Boot) เป็นค่าที่ต้องการ — ตัวอย่างเช่น 100 MB
ได้ multipart/form-data รองรับหลายไฟล์ในคำขอเดียว แต่ละไฟล์ถูกส่งเป็นส่วนแยกต่างหากพร้อม Content-Disposition และ Content-Type ของตัวเอง ฟอร์ม HTML ใช้แอตทริบิวต์ multiple สำหรับ input type="file" ใน OkHttp เรียก addFormDataPart สำหรับแต่ละไฟล์ ใน Alamofire เรียก append สำหรับแต่ละไฟล์
Boundary เป็นสตริงที่ไม่ซ้ำกันซึ่งแยกส่วนต่างๆ ของคำขอแบบประกอบและช่วยให้เซิร์ฟเวอร์ระบุว่าส่วนหนึ่งสิ้นสุดที่ใดและอีกส่วนเริ่มต้นที่ใด มันถูกสร้างโดยไคลเอ็นต์และระบุในส่วนหัว Content-Type หากไม่มี boundary เซิร์ฟเวอร์จะไม่สามารถแยกวิเคราะห์คำขอแบบหลายองค์ประกอบเป็นฟิลด์และไฟล์แต่ละรายการได้
อย่าเชื่อถือนามสกุลไฟล์หรือ Content-Type จากคำขอ — ผู้โจมตีสามารถปลอมแปลงได้ ตรวจสอบชนิด MIME ผ่าน magic bytes (ไบต์แรกของไฟล์): Apache Tika บน Java, libmagic บน C/C++, คำสั่ง file บน Linux หรือเครื่องมือในตัวของเฟรมเวิร์ก — Files.probeContentType() บน Java, mimetypes บน Python
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม