Multipart Upload trong phát triển web: bản chất, cấu trúc và cách multipart/form-data hoạt động

Tác giả: IT Sectr Đã đăng: 2026-03-10 Thời gian đọc: 9 phút

Multipart Upload là một cơ chế HTTP cho phép truyền nhiều phần dữ liệu không đồng nhất trong một yêu cầu duy nhất, bao gồm các trường văn bản và tệp nhị phân. Mỗi phần được phân tách bằng một chuỗi ranh giới duy nhất và có header Content-Type riêng. Theo MDN Web Docs, 2025, multipart/form-data là định dạng tiêu chuẩn để tải tệp lên qua biểu mẫu HTML và được sử dụng rộng rãi trong các ứng dụng web và di động để gửi hình ảnh, tài liệu và các tệp khác đến máy chủ.

Các điểm chính

  • Multipart Upload — truyền nhiều phần dữ liệu trong một yêu cầu HTTP duy nhất được phân tách bằng boundary.
  • multipart/form-data — loại MIME tiêu chuẩn để tải tệp lên từ biểu mẫu HTML và ứng dụng di động.
  • Boundary — một chuỗi duy nhất phân tách các phần của yêu cầu tổng hợp, được tạo tự động bởi các máy khách HTTP.
  • Mỗi phần chứa các header Content-Disposition và Content-Type mô tả tên trường và loại tệp.
  • Multipart Upload hiệu quả hơn nhiều yêu cầu riêng lẻ — một POST thay thế N lần gọi riêng biệt đến máy chủ.

Multipart Upload là gì?

Multipart Upload là một phương pháp truyền dữ liệu qua giao thức HTTP trong đó phần thân yêu cầu bao gồm nhiều phần được phân tách một cách logic. Mỗi phần có thể chứa dữ liệu thuộc loại khác nhau: trường biểu mẫu văn bản, tệp nhị phân, đối tượng JSON hoặc hình ảnh. Tất cả các phần được đóng gói vào một yêu cầu POST duy nhất, loại bỏ nhu cầu gửi N lần gọi HTTP riêng biệt. Multipart Upload là một phần thiết yếu của biểu mẫu web và API tải tệp lên.

Định dạng multipart được định nghĩa trong đặc tả RFC 2046 như một phần của tiêu chuẩn MIME cho tin nhắn email, sau đó được điều chỉnh cho HTTP trong RFC 1867. Ngày nay, phát triển web sử dụng hầu như độc quyền multipart/form-data — một trong các loại con multipart được thiết kế cho biểu mẫu chứa tệp. Các loại con khác — multipart/mixed (cho tệp đính kèm tùy ý) và multipart/byteranges (cho tải xuống một phần tệp) — được sử dụng ít thường xuyên hơn nhiều.

Sự khác biệt cơ bản giữa multipart và application/x-www-form-urlencoded là loại sau mã hóa tất cả dữ liệu thành chuỗi tương thích URI và không hỗ trợ tệp nhị phân. Ngược lại, Multipart/form-data truyền từng tệp ở dạng nhị phân gốc mà không cần mã hóa, hiệu quả hơn và không mất độ chính xác. Kích thước yêu cầu với multipart luôn lớn hơn 5-15% so với tổng kích thước tệp do chi phí header của các phần và ranh giới.

Khi nào Multipart Upload được sử dụng

Multipart Upload được sử dụng ở mọi nơi cần tải tệp lên: ảnh đại diện và ảnh hồ sơ trên mạng xã hội, tệp đính kèm trong trình nhắn tin, tài liệu trong hệ thống CRM, hình ảnh sản phẩm trong cửa hàng trực tuyến. Trong ứng dụng di động, Multipart Upload được sử dụng để gửi tệp phương tiện đến máy chủ — ảnh từ máy ảnh thiết bị, bản ghi âm giọng nói, clip video. Theo Cloudflare Research, khoảng 15% tất cả các yêu cầu POST trên web sử dụng multipart/form-data.

Sự khác biệt giữa multipart và chunked transfer

Multipart Upload và Chunked Transfer là các cơ chế khác nhau. Multipart chia yêu cầu thành các phần có nội dung (trường và tệp), trong khi Chunked Transfer chia luồng dữ liệu thành các đoạn để truyền mà không cần biết tổng kích thước. Multipart có thể được truyền bên trong Chunked Transfer: máy chủ gửi phản hồi multipart theo từng phần mà không biết kích thước đầy đủ của nó. Các cơ chế này không xung đột và giải quyết các vấn đề khác nhau ở các cấp độ khác nhau.

Cách multipart/form-data hoạt động

Khi trình duyệt gửi biểu mẫu có thuộc tính enctype="multipart/form-data", nó xây dựng phần thân yêu cầu ở định dạng multipart. Mỗi trường biểu mẫu trở thành một khối riêng biệt, được phân tách khỏi các khối khác bằng chuỗi ranh giới (boundary). Ranh giới được tạo tự động và là một chuỗi ký tự duy nhất được đảm bảo không xuất hiện trong dữ liệu. Máy khách thêm ranh giới này vào header Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryX7K.

Mỗi khối bắt đầu bằng --boundary và chứa header Content-Disposition với tên trường (name) và đối với tệp, tên tệp gốc (filename). Sau một dòng trống là dữ liệu trường hoặc nội dung tệp ở dạng nhị phân. Yêu cầu kết thúc bằng chuỗi --boundary--. Máy chủ phân tích luồng nhận được: đầu tiên tìm ranh giới, sau đó trích xuất header của mỗi phần, xác định loại dữ liệu và chuyển chúng đến trình xử lý biểu mẫu hoặc bộ điều khiển API.

Theo IETF RFC 7578, multipart/form-data không yêu cầu chỉ định charset cho mỗi phần vì các trường văn bản được coi là UTF-8 và các phần nhị phân chứa tệp ở mã hóa gốc của chúng. Kích thước của một phần không bị giới hạn bởi giao thức — các giới hạn được đặt ở cấp máy chủ: ví dụ: trong Nginx qua client_max_body_size, trong Spring Boot qua spring.servlet.multipart.max-file-size.

Định dạng Boundary và cách tạo

Boundary là một chuỗi duy nhất không được xuất hiện trong dữ liệu được truyền. Nó thường bắt đầu bằng tiền tố (ví dụ: ----WebKitFormBoundary hoặc ----Boundary) và chứa các ký tự ngẫu nhiên. Trình duyệt và máy khách HTTP tạo boundary tự động. Chiều dài boundary không được vượt quá 70 ký tự theo RFC 2046. Mỗi phần được phân tách bằng chuỗi --boundary\r\n và kết thúc yêu cầu được đánh dấu bằng --boundary--\r\n.

Cấu trúc yêu cầu multipart

Yêu cầu multipart có cấu trúc chặt chẽ được xác định bởi các tiêu chuẩn MIME và HTTP. Header yêu cầu đặt Content-Type: multipart/form-data với tham số boundary. Phần thân yêu cầu bao gồm một chuỗi các phần, mỗi phần chứa header và thân riêng. Header phần bao gồm Content-Disposition (bắt buộc) và Content-Type (tùy chọn — cho tệp). Một dòng trống giữa header phần và dữ liệu của nó là bắt buộc.

Phần tửVí dụBắt buộc
Content-Typemultipart/form-data; boundary=---Bnd123
Dấu phân cách phần---Bnd123Có (trước mỗi phần)
Content-Dispositionform-data; name="avatar"; filename="photo.jpg"
Content-Type phầnimage/jpegCho tệp
Thân phần[dữ liệu hình ảnh nhị phân]
Ranh giới kết thúc---Bnd123--Có (kết thúc yêu cầu)

Ví dụ về yêu cầu multipart

Hãy xem xét một ví dụ thực tế về yêu cầu multipart gửi một trường văn bản và một tệp hình ảnh. Máy khách tạo header Content-Type với boundary duy nhất. Phần thân yêu cầu chứa tuần tự tất cả các trường biểu mẫu. Khi nhận được, máy chủ phân tích các phần này và cung cấp cho nhà phát triển quyền truy cập vào từng trường như một đối tượng riêng biệt. Cách tiếp cận này cho phép xử lý các biểu mẫu phức tạp có tệp trong một lần gọi HTTP duy nhất.

kotlin
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("Đã tải lên: ${response.isSuccessful}")
    }
}

Phân tích phản hồi multipart trên máy chủ

Phía máy chủ, yêu cầu multipart được phân tích bởi framework hoặc thủ công. Trong Spring Boot, chỉ cần chú thích @RequestParam("avatar") MultipartFile file và framework tự động trích xuất tệp từ yêu cầu multipart. Trong Ktor trên Kotlin, sử dụng receiveMultipart(), trong Express.js — middleware multer. Máy chủ có quyền truy cập vào từng trường biểu mẫu và từng tệp đã tải lên một cách độc lập, lưu tệp vào đĩa hoặc lưu trữ đám mây và trả về URL hoặc định danh cho máy khách.

Lợi ích của tải lên đa thành phần

Multipart Upload cung cấp một số lợi thế chính so với các phương pháp truyền dữ liệu thay thế. Một yêu cầu thay vì nhiều yêu cầu — tất cả các trường biểu mẫu và tệp được truyền trong một lần gọi HTTP duy nhất, giảm tải cho mạng và máy chủ. Không cần mở N kết nối để tải lên N tệp — mọi thứ được đóng gói trong một POST. Điều này đặc biệt quan trọng đối với ứng dụng di động, nơi mỗi kết nối HTTP đồng nghĩa với độ trễ và tiêu hao pin.

Truyền nhị phân không cần mã hóa — không giống như application/x-www-form-urlencoded, nơi dữ liệu nhị phân được mã hóa base64 (tăng kích thước 33%), multipart/form-data truyền tệp ở dạng nhị phân gốc. Điều này hiệu quả hơn về kích thước và tốc độ. Đối với các tệp lớn hơn 10 MB, sự khác biệt trở nên quan trọng: yêu cầu multipart sẽ nhỏ hơn 30% so với yêu cầu mã hóa URL với cùng một tệp.

Cấu trúc linh hoạt — multipart cho phép kết hợp các trường thuộc loại khác nhau theo bất kỳ thứ tự nào. Một biểu mẫu có thể chứa đồng thời các trường văn bản, nhiều tệp, dữ liệu JSON và trường ẩn. Mỗi phần có Content-Type riêng, cho phép trộn dữ liệu văn bản và nhị phân. Để so sánh: mã hóa base64 thêm 33% vào kích thước, trong khi multipart chỉ thêm khoảng 5-15% cho các header dịch vụ.

So sánh multipart với các định dạng truyền khác

Theo nghiên cứu của HTTP Archive, 2025, multipart/form-data được sử dụng trong 94% trường hợp tải tệp lên trên web. Các lựa chọn thay thế — base64 trong JSON (4%) và truyền trực tiếp qua WebSocket (2%). JSON với base64 thuận tiện cho API nơi tất cả dữ liệu khác cũng ở dạng JSON, nhưng không hiệu quả cho các tệp lớn. WebSocket phù hợp cho dữ liệu thời gian thực nhưng không được tất cả cơ sở hạ tầng HTTP hỗ trợ. Multipart vẫn là tiêu chuẩn cho tải tệp lên nhờ tính đơn giản và hiệu quả.

Multipart Upload trong phát triển di động

Trong ứng dụng di động, Multipart Upload được sử dụng để gửi nội dung đa phương tiện từ thiết bị người dùng: ảnh từ thư viện, ảnh chụp từ máy ảnh, bản ghi âm giọng nói, tệp tài liệu. Trên Android, cách tiếp cận tiêu chuẩn là OkHttp với MultipartBody.Builder, cho phép dễ dàng tạo yêu cầu multipart. Retrofit cũng hỗ trợ multipart qua các chú thích @Multipart và @Part. Nhà phát triển chỉ định loại dữ liệu cho mỗi phần và máy khách HTTP tự động tạo các header chính xác.

Trên iOS, các tác vụ tương tự được giải quyết bằng URLSession với HTTPBodyStream tùy chỉnh hoặc qua Alamofire với multipartFormData. Alamofire cung cấp phương thức upload(multipartFormData:) thuận tiện để gửi yêu cầu multipart. Trên cả hai nền tảng, cần xem xét kích thước tệp được tải lên — đối với các tệp lớn (hơn 10-20 MB), nên sử dụng tải lên nền để ứng dụng không bị kết thúc khi thu nhỏ. Trên Android, điều này được thực hiện qua DownloadManager hoặc WorkManager; trên iOS — qua URLSession với cấu hình nền.

Khi tải tệp lên trong ứng dụng di động, cần xem xét trạng thái mạng. Connectivity Manager trên Android giúp xác định Wi-Fi hay dữ liệu di động có sẵn và chọn thời điểm tối ưu để tải lên. Đối với các tệp lớn như video, nên hoãn tải lên cho đến khi kết nối Wi-Fi để không tiêu tốn dữ liệu di động của người dùng. WorkManager trên Android cho phép đặt các ràng buộc như vậy qua NetworkType.UNMETERED.

Tối ưu hóa tải lên: nén và thay đổi kích thước

Trước khi gửi tệp qua Multipart Upload, ứng dụng di động thường nén và thay đổi kích thước hình ảnh. Nén JPEG với chất lượng 85% giảm kích thước tệp 3-5 lần mà không mất chất lượng đáng kể khi xem trên màn hình. Thay đổi kích thước hình ảnh xuống 1920px ở cạnh dài hơn giúp giảm thêm kích thước. Trên Android, sử dụng Bitmap.compress(); trên iOS — UIImageJPEGRepresentation với tham số nén 0,85. Việc tối ưu hóa như vậy giúp tăng tốc tải lên và tiết kiệm dữ liệu di động.

Lỗi và giới hạn của Multipart Upload

Lỗi phổ biến nhất khi Multipart Upload là vượt quá giới hạn kích thước yêu cầu trên máy chủ. Theo mặc định, Nginx giới hạn kích thước phần thân yêu cầu ở 1 MB (client_max_body_size) và Tomcat ở 2 MB (maxSwallowSize). Nếu nhà phát triển không tăng các giới hạn này, máy chủ sẽ trả về lỗi 413 Request Entity Too Large. Giải pháp là định cấu hình rõ ràng kích thước tải lên tối đa trên máy chủ và hiển thị cảnh báo trên máy khách nếu tệp vượt quá kích thước cho phép.

Vấn đề thứ hai là xử lý không chính xác các yêu cầu multipart trong quá trình truyền phát phần thân. Một số máy chủ cố gắng tải toàn bộ yêu cầu multipart vào bộ nhớ trước khi phân tích, dẫn đến OutOfMemoryError cho các tệp lớn. Các máy chủ hiện đại (Nginx, Spring Boot, Ktor) hỗ trợ phân tích luồng multipart, nơi mỗi phần được xử lý khi đến. Nhà phát triển phải đảm bảo máy chủ được cấu hình để xử lý luồng các yêu cầu multipart.

Loại vấn đề thứ ba là hết thời gian chờ khi tải lên các tệp lớn. Máy khách HTTP có cài đặt readTimeout và connectTimeout có thể kích hoạt trong quá trình tải lên kéo dài các tệp lớn hơn 50-100 MB. Giải pháp là tăng thời gian chờ cho các điểm cuối tải lên hoặc sử dụng mã hóa truyền chunked trong multipart. Trên thiết bị di động, cũng cần xử lý gián đoạn tải lên và triển khai tiếp tục (resume) khi mất kết nối.

Bảo mật Multipart Upload

Tải tệp lên qua multipart là một trong những điểm cuối dễ bị tổn thương nhất của ứng dụng web. Kẻ tấn công có thể tải lên tập lệnh thực thi bằng cách đổi tên nó thành image.jpg. Máy chủ phải kiểm tra loại MIME của tệp đã tải lên không phải bằng phần mở rộng mà bằng nội dung (magic bytes), giới hạn các loại được phép và quét tệp bằng phần mềm diệt virus. Nên lưu trữ các tệp đã tải lên bên ngoài document-root của máy chủ web và phân phối chúng qua bộ điều khiển riêng biệt với kiểm tra quyền truy cập.

Các câu hỏi thường gặp

multipart/form-data khác với application/x-www-form-urlencoded như thế nào?

multipart/form-data truyền từng trường biểu mẫu như một khối riêng biệt với header riêng và hỗ trợ tệp nhị phân không cần mã hóa. application/x-www-form-urlencoded mã hóa tất cả dữ liệu thành chuỗi tương thích URI (khóa=giá trị&khóa2=giá trị2) và không hỗ trợ trực tiếp tệp — chúng phải được mã hóa base64.

Kích thước tệp tối đa cho Multipart Upload là bao nhiêu?

Giao thức HTTP không giới hạn kích thước yêu cầu multipart, nhưng trên thực tế, các giới hạn được đặt bởi máy chủ. Nginx mặc định giới hạn ở 1 MB, Apache ở 2 MB, Spring Boot ở 1 MB. Để tải lên các tệp lớn, hãy định cấu hình client_max_body_size (Nginx) hoặc spring.servlet.multipart.max-file-size (Spring Boot) đến giá trị mong muốn — ví dụ: 100 MB.

Có thể gửi nhiều tệp trong một yêu cầu multipart không?

Có, multipart/form-data hỗ trợ nhiều tệp trong một yêu cầu duy nhất. Mỗi tệp được truyền như một phần riêng biệt với Content-Disposition và Content-Type riêng. Biểu mẫu HTML sử dụng thuộc tính multiple cho input type="file". Trong OkHttp, addFormDataPart được gọi cho mỗi tệp; trong Alamofire, append được gọi cho mỗi tệp.

Tại sao cần boundary trong yêu cầu multipart?

Boundary là một chuỗi duy nhất phân tách các phần của yêu cầu tổng hợp và cho phép máy chủ xác định nơi một phần kết thúc và phần khác bắt đầu. Nó được tạo bởi máy khách và được chỉ định trong header Content-Type. Nếu không có boundary, máy chủ không thể phân tích yêu cầu đa thành phần thành các trường và tệp riêng lẻ.

Làm thế nào để kiểm tra loại tệp đã tải lên trên máy chủ?

Đừng dựa vào phần mở rộng tệp hoặc Content-Type từ yêu cầu — kẻ tấn công có thể giả mạo chúng. Hãy kiểm tra loại MIME qua magic bytes (các byte đầu tiên của tệp): Apache Tika trên Java, libmagic trên C/C++, lệnh file trên Linux hoặc các công cụ tích hợp của framework — Files.probeContentType() trên Java, mimetypes trên Python.

Tổng kết

  • Multipart Upload — cơ chế truyền nhiều phần không đồng nhất trong một yêu cầu HTTP duy nhất được phân tách bằng boundary.
  • multipart/form-data — loại MIME tiêu chuẩn để tải tệp lên qua biểu mẫu web và ứng dụng di động, hỗ trợ truyền nhị phân không cần mã hóa.
  • Mỗi phần của yêu cầu chứa header Content-Disposition và Content-Type riêng, cho phép gửi các trường thuộc loại khác nhau trong một yêu cầu.
  • Boundary — chuỗi phân cách duy nhất, được tạo tự động bởi máy khách, không được xuất hiện trong dữ liệu được truyền.
  • Lợi ích — một yêu cầu thay vì nhiều yêu cầu, truyền nhị phân không cần mã hóa base64, hỗ trợ tệp mọi kích thước (với cấu hình máy chủ phù hợp).
  • Giới hạn — giới hạn kích thước máy chủ, hết thời gian chờ khi tải tệp lớn, nguy cơ OutOfMemoryError nếu không xử lý luồng.
  • Bảo mật — kiểm tra loại MIME bằng nội dung tệp, không phải phần mở rộng; lưu trữ tệp bên ngoài document-root và quét virus.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm