Quentin đã đưa ra câu trả lời chính xác: sử dụng multipart/form-data khi biểu mẫu chứa các tệp tải lên và sử dụng application/x-www-form-urlencoded cho các trường hợp còn lại. Đây là định dạng mặc định nếu bạn bỏ qua thuộc tính enctype. Bài viết này sẽ đi sâu hơn vào lý do tại sao điều này đúng, kèm theo các ví dụ minh họa cụ thể.
Mục Lục
I. Tham Khảo Tiêu Chuẩn HTML5
HTML5 định nghĩa ba giá trị có thể cho thuộc tính enctype của thẻ <form>:
application/x-www-form-urlencodedmultipart/form-data(chi tiết kỹ thuật được mô tả trong RFC7578)text/plain(ít an toàn và không nên sử dụng trong môi trường thực tế)
Chúng ta sẽ tập trung vào hai loại đầu tiên để hiểu rõ hơn về cách chúng hoạt động.
II. Ví Dụ Thực Tế
Để hiểu rõ hơn sự khác biệt, hãy xem xét một ví dụ đơn giản về cách mỗi phương thức này hoạt động.
Chúng ta sẽ sử dụng các công cụ sau:
nc -lhoặc một máy chủ ECHO đơn giản để lắng nghe các yêu cầu HTTP.- Trình duyệt web (ví dụ: Firefox, Chrome) hoặc
cURLđể gửi biểu mẫu.
-
Tạo Biểu Mẫu HTML:
Lưu đoạn mã sau vào một tệp
.html(ví dụ:upload.html):<!DOCTYPE html> <html> <head> <title>Upload Form</title> </head> <body> <form action="http://localhost:8000" method="post" enctype="multipart/form-data"> Text 1: <input type="text" name="text1" value="text default"><br> Text 2: <input type="text" name="text2" value="aωb"><br> File 1: <input type="file" name="file1"><br> File 2: <input type="file" name="file2"><br> File 3: <input type="file" name="file3"><br> <input type="submit" value="Submit"> </form> </body> </html>Trong ví dụ này, chúng ta thiết lập giá trị mặc định cho trường
text2làaωb, trong đóωlà ký tự Omega (U+03C9), được biểu diễn bằng các byte61 CF 89 62trong UTF-8. -
Tạo Các Tệp Để Tải Lên:
Tạo ba tệp với nội dung khác nhau:
echo 'Content of a.txt.' > a.txt echo '<p>Content of a.html.</p>' > a.html printf 'axCFx89b' > binaryTệp
binarychứa 4 byte:a,0xCF,0x89vàb. -
Khởi Động Máy Chủ Echo:
Sử dụng lệnh sau để tạo một máy chủ echo đơn giản lắng nghe trên cổng 8000:
while true; do printf "" | nc -l 8000 localhost; done -
Thử Nghiệm:
Mở tệp
upload.htmltrong trình duyệt, chọn các tệp bạn vừa tạo và nhấn nút “Submit”. Kiểm tra đầu ra trên thiết bị đầu cuối nơi bạn đang chạync.ncsẽ in ra yêu cầu HTTP đã nhận được.
III. Phân Tích multipart/form-data
Khi sử dụng multipart/form-data, trình duyệt (trong trường hợp này là Firefox) sẽ gửi một yêu cầu tương tự như sau:
POST / HTTP/1.1
... (các header khác) ...
Content-Type: multipart/form-data; boundary=-735323031399963166993862150
Content-Length: 834
--735323031399963166993862150
Content-Disposition: form-data; name="text1"
text default
--735323031399963166993862150
Content-Disposition: form-data; name="text2"
aωb
--735323031399963166993862150
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain
Content of a.txt.
--735323031399963166993862150
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html
<p>Content of a.html.</p>
--735323031399963166993862150
Content-Disposition: form-data; name="file3"; filename="binary"
Content-Type: application/octet-stream
aωb
--735323031399963166993862150--
Phân tích chi tiết:
-
Content-Type: multipart/form-data; boundary=-735323031399963166993862150: Xác định loại nội dung làmultipart/form-datavà chỉ định chuỗiboundarydùng để phân tách các trường. Lưu ý rằng chuỗiboundarythực tế bắt đầu bằng hai dấu gạch ngang (--). RFC 7578 quy định điều này:“Các phần được phân tách bằng dấu phân cách ranh giới, được xây dựng bằng CRLF, “-” và giá trị của tham số “ranh giới”.”
-
Mỗi trường có các header riêng:
Content-Disposition: form-data;,namecủa trường,filename(nếu là tệp) vàContent-Typetương ứng. -
Dữ liệu được gửi nguyên trạng cho cả trường văn bản và tệp nhị phân. Ví dụ, chuỗi
aωb(trong UTF-8) được gửi dưới dạng các byte61 CF 89 62.
Trình duyệt phải chọn một chuỗi boundary duy nhất, không xuất hiện trong bất kỳ dữ liệu nào của các trường. Điều này đảm bảo việc phân tách chính xác các phần dữ liệu.
IV. Phân Tích application/x-www-form-urlencoded
Bây giờ, hãy thay đổi enctype thành application/x-www-form-urlencoded, tải lại trang và gửi lại biểu mẫu.
<form action="http://localhost:8000" method="post" enctype="application/x-www-form-urlencoded">
Trình duyệt sẽ gửi một yêu cầu như sau:
POST / HTTP/1.1
... (các header khác) ...
Content-Type: application/x-www-form-urlencoded
Content-Length: 51
text1=text+default&text2=a%CF%89b&file1=a.txt&file2=a.html&file3=binary
Trong trường hợp này:
- Dữ liệu của các tệp không được gửi, chỉ có tên tệp.
- Các ký tự đặc biệt và khoảng trắng được mã hóa: khoảng trắng thành
+, các ký tự không in được thành%xx(trong đóxxlà mã hex của ký tự). Ví dụ,ωđược mã hóa thành%CF%89, chiếm 3 byte.
V. So Sánh và Kết Luận
Từ các ví dụ trên, chúng ta có thể rút ra những kết luận sau:
-
multipart/form-data: Thích hợp cho việc tải lên tệp, vì nó cho phép gửi dữ liệu nhị phân mà không cần mã hóa. Có thêm chi phí cho chuỗiboundary, nhưng mỗi byte dữ liệu được gửi bằng một byte. -
application/x-www-form-urlencoded: Không phù hợp cho việc tải lên tệp, vì nó chỉ gửi tên tệp và mã hóa các ký tự không in được. Điều này dẫn đến tăng kích thước dữ liệu đáng kể (mỗi ký tự không in được chiếm 3 byte).
Tóm lại:
- Sử dụng
multipart/form-datakhi biểu mẫu chứa các tệp cần tải lên. - Sử dụng
application/x-www-form-urlencodedcho các biểu mẫu chỉ chứa dữ liệu văn bản đơn giản.
Ngay cả khi có thể gửi tệp bằng application/x-www-form-urlencoded, nó sẽ rất kém hiệu quả do việc mã hóa dữ liệu. Đối với các trường văn bản chứa chủ yếu các ký tự in được, sự khác biệt về hiệu quả không đáng kể, do đó application/x-www-form-urlencoded vẫn là một lựa chọn hợp lý trong trường hợp này.
