application / x-www-form-urlencoding hoặc multiart / form-data?


1335

Trong HTTP có hai cách để POST dữ liệu: application/x-www-form-urlencodedmultipart/form-data. Tôi hiểu rằng hầu hết các trình duyệt chỉ có thể tải lên các tệp nếu multipart/form-datađược sử dụng. Có hướng dẫn bổ sung nào khi sử dụng một trong các loại mã hóa trong ngữ cảnh API (không có trình duyệt liên quan) không? Điều này có thể ví dụ dựa trên:

  • kích thước dữ liệu
  • sự tồn tại của các ký tự không phải ASCII
  • tồn tại trên dữ liệu nhị phân (chưa được mã hóa)
  • sự cần thiết phải chuyển dữ liệu bổ sung (như tên tệp)

Về cơ bản, tôi không tìm thấy hướng dẫn chính thức nào trên web về việc sử dụng các loại nội dung khác nhau cho đến nay.


74
Cần phải đề cập rằng đây là hai loại MIME mà biểu mẫu HTML sử dụng. Bản thân HTTP không có giới hạn như vậy ... người ta có thể sử dụng bất kỳ loại MIME nào mình muốn thông qua HTTP.
tybro0103

Câu trả lời:


2013

TL; DR

Tóm lược; nếu bạn có dữ liệu nhị phân (không phải chữ và số) (hoặc tải trọng có kích thước đáng kể) để truyền, hãy sử dụng multipart/form-data. Nếu không, sử dụng application/x-www-form-urlencoded.


Các loại MIME mà bạn đề cập là hai Content-Typetiêu đề cho các yêu cầu POST HTTP mà tác nhân người dùng (trình duyệt) phải hỗ trợ. Mục đích của cả hai loại yêu cầu này là gửi danh sách các cặp tên / giá trị đến máy chủ. Tùy thuộc vào loại và lượng dữ liệu được truyền, một trong các phương pháp sẽ hiệu quả hơn các phương pháp khác. Để hiểu lý do tại sao, bạn phải nhìn vào những gì mỗi người đang làm dưới vỏ bọc.

Đối với application/x-www-form-urlencoded, phần thân của thông điệp HTTP được gửi đến máy chủ về cơ bản là một chuỗi truy vấn khổng lồ - các cặp tên / giá trị được phân tách bằng ký hiệu ( &) và các tên được phân tách khỏi các giá trị bằng ký hiệu bằng ( =). Một ví dụ về điều này sẽ là: 

MyVariableOne=ValueOne&MyVariableTwo=ValueTwo

Theo đặc điểm kỹ thuật :

[Ký tự và] ký tự không chữ và số được thay thế bằng `% HH ', ký hiệu phần trăm và hai chữ số thập lục phân đại diện cho mã ASCII của ký tự

Điều đó có nghĩa là với mỗi byte không chữ và số tồn tại trong một trong các giá trị của chúng tôi, nó sẽ lấy ba byte để biểu thị nó. Đối với các tệp nhị phân lớn, việc tăng gấp ba lần tải trọng sẽ không hiệu quả cao.

Đó là nơi multipart/form-dataxuất hiện. Với phương thức truyền các cặp tên / giá trị này, mỗi cặp được biểu diễn dưới dạng "một phần" trong thông báo MIME (như được mô tả bởi các câu trả lời khác). Các bộ phận được phân tách bằng một ranh giới chuỗi cụ thể (được chọn cụ thể để chuỗi ranh giới này không xảy ra trong bất kỳ tải trọng "giá trị" nào). Mỗi phần có một bộ tiêu đề MIME riêng Content-Type, và đặc biệt Content-Disposition, có thể đặt cho mỗi phần là "tên". Phần giá trị của mỗi cặp tên / giá trị là tải trọng của từng phần của thông báo MIME. Thông số MIME cung cấp cho chúng tôi nhiều tùy chọn hơn khi biểu thị tải trọng giá trị - chúng tôi có thể chọn mã hóa dữ liệu nhị phân hiệu quả hơn để tiết kiệm băng thông (ví dụ: cơ sở 64 hoặc thậm chí là nhị phân thô).

Tại sao không sử dụng multipart/form-datatất cả thời gian? Đối với các giá trị chữ và số ngắn (như hầu hết các biểu mẫu web), chi phí chung của việc thêm tất cả các tiêu đề MIME sẽ vượt xa đáng kể bất kỳ khoản tiết kiệm nào từ mã hóa nhị phân hiệu quả hơn.


84
X-www-form-urlencoding có giới hạn độ dài hay không giới hạn?
Pacerier

34
@Pacerier Giới hạn được thi hành bởi máy chủ nhận yêu cầu POST. Xem chủ đề này để biết thêm thảo luận: stackoverflow.com/questions/2364840/ từ
Matt Bridges

5
@ZiggyTheroulette JSON và BSON đều hiệu quả hơn cho các loại dữ liệu khác nhau. Base64 kém hơn gzip, cho cả hai phương thức tuần tự hóa. Base64 hoàn toàn không mang lại bất kỳ lợi thế nào, HTTP hỗ trợ các tải trọng nhị phân.
Tiberiu-Ionuț Stan

16
Cũng lưu ý rằng nếu một biểu mẫu chứa tệp tải lên có tên, lựa chọn duy nhất của bạn là dữ liệu biểu mẫu, vì urlencoding không có cách đặt tên tệp (trong dữ liệu biểu mẫu đó là tham số tên để xử lý nội dung).
Guido van Rossum

4
@EML thấy cha mẹ của tôi "(được chọn cụ thể để chuỗi ranh giới này không xảy ra trong bất kỳ" tải trọng "giá trị nào)"
Matt Bridges

151

ĐỌC LẠI PARA ĐẦU TIÊN TẠI ĐÂY!

Tôi biết rằng đây là 3 năm quá muộn, nhưng câu trả lời (được chấp nhận) của Matt không đầy đủ và cuối cùng sẽ khiến bạn gặp rắc rối. Chìa khóa ở đây là, nếu bạn chọn sử dụng multipart/form-data, ranh giới không được xuất hiện trong dữ liệu tệp mà cuối cùng máy chủ nhận được.

Đây không phải là một vấn đề cho application/x-www-form-urlencoded, bởi vì không có ranh giới. x-www-form-urlencodedcũng có thể luôn luôn xử lý dữ liệu nhị phân, bằng cách đơn giản là biến một byte tùy ý thành ba 7BITbyte. Không hiệu quả, nhưng nó hoạt động (và lưu ý rằng nhận xét về việc không thể gửi tên tệp cũng như dữ liệu nhị phân là không chính xác; bạn chỉ cần gửi nó dưới dạng cặp khóa / giá trị khác).

Vấn đề với multipart/form-datalà không phải có dấu phân cách ranh giới trong dữ liệu tệp (xem RFC 2388 ; phần 5.2 cũng bao gồm một lý do khá khập khiễng vì không có loại MIME tổng hợp phù hợp để tránh vấn đề này).

Vì vậy, ngay từ cái nhìn đầu tiên, multipart/form-datakhông có giá trị gì trong bất kỳ tập tin tải lên, nhị phân hay cách khác. Nếu bạn không chọn đúng ranh giới của mình, thì cuối cùng bạn sẽ gặp vấn đề, cho dù bạn đang gửi văn bản thuần túy hoặc nhị phân thô - máy chủ sẽ tìm thấy một ranh giới ở sai vị trí và tệp của bạn sẽ bị cắt bớt hoặc POST sẽ thất bại.

Điều quan trọng là chọn mã hóa và ranh giới sao cho các ký tự ranh giới được chọn của bạn không thể xuất hiện trong đầu ra được mã hóa. Một giải pháp đơn giản là sử dụng base64( không sử dụng nhị phân thô). Trong base64 3 byte tùy ý được mã hóa thành bốn ký tự 7 bit, trong đó bộ ký tự đầu ra là [A-Za-z0-9+/=](tức là chữ và số, '+', '/' hoặc '='). =là trường hợp đặc biệt và chỉ có thể xuất hiện ở cuối đầu ra được mã hóa, dưới dạng đơn =hoặc kép ==. Bây giờ, chọn ranh giới của bạn dưới dạng chuỗi ASCII 7 bit không thể xuất hiện ở base64đầu ra. Nhiều lựa chọn bạn thấy trên mạng thất bại trong bài kiểm tra này - MDN tạo thành tài liệu, ví dụ: sử dụng "blob" làm ranh giới khi gửi dữ liệu nhị phân - không tốt. Tuy nhiên, một cái gì đó như "! Blob!" sẽ không bao giờ xuất hiện trong base64đầu ra.


52
Mặc dù việc xem xét nhiều dữ liệu / biểu mẫu là đảm bảo ranh giới không xuất hiện trong dữ liệu, điều này khá đơn giản để thực hiện bằng cách chọn một ranh giới đủ dài. Xin vui lòng không chúng tôi mã hóa base64 để thực hiện điều này. Một ranh giới được tạo ngẫu nhiên và có cùng độ dài với UUID là đủ: stackoverflow.com/questions/1705008/ .
Joshcodes

20
@EML, Điều này không có ý nghĩa gì cả. Rõ ràng ranh giới được chọn tự động bởi ứng dụng khách http (trình duyệt) và ứng dụng khách sẽ đủ thông minh để không sử dụng ranh giới xung đột với nội dung của các tệp đã tải lên của bạn. Đó là một chuỗi con aa đơn giản phù hợp index === -1.
Pacerier

13
@Pacerier: (A) đọc câu hỏi: "không có trình duyệt liên quan, bối cảnh API". (B) trình duyệt không xây dựng yêu cầu cho bạn nào. Bạn tự làm, bằng tay. Không có phép thuật trong trình duyệt.
EML

12
@BeniBela, có lẽ anh ấy sẽ đề nghị sử dụng '()+-./:=sau đó. Tuy nhiên, việc tạo ngẫu nhiên với kiểm tra chuỗi con vẫn là cách để thực hiện và nó có thể được thực hiện với một dòng : while(true){r = rand(); if(data.indexOf(r) === -1){doStuff();break;}}. Đề xuất của EML (chuyển đổi sang base64 chỉ để tránh kết hợp các chuỗi con) chỉ là số lẻ, chưa kể nó đi kèm với sự suy giảm hiệu suất không cần thiết. Và tất cả những rắc rối không có gì vì thuật toán một dòng cũng đơn giản và đơn giản như nhau. Base64 không có nghĩa là (ab) được sử dụng theo cách này, vì cơ thể HTTP chấp nhận tất cả các octet 8 bit .
Pacerier

31
Câu trả lời này không chỉ không thêm gì vào cuộc thảo luận mà còn đưa ra lời khuyên sai. Thứ nhất, bất cứ khi nào truyền dữ liệu ngẫu nhiên trong các phần riêng biệt, luôn có khả năng ranh giới được chọn sẽ có mặt trong tải trọng. Cách DUY NHẤT để đảm bảo điều này không xảy ra là kiểm tra toàn bộ trọng tải cho từng ranh giới chúng tôi đưa ra. Hoàn toàn không thực tế. Chúng tôi chỉ chấp nhận xác suất va chạm vô cùng lớn và đưa ra một ranh giới hợp lý, như "--- ranh giới- <UUID ở đây> -ienary ---". Thứ hai, luôn luôn sử dụng Base64 sẽ lãng phí băng thông và lấp đầy bộ đệm mà không có lý do nào cả.
âm đạo

92

Tôi không nghĩ HTTP bị giới hạn ở POST trong nhiều phần hoặc x-www-form-urlencoding. Các Content-Type header là trực giao với phương thức HTTP POST (bạn có thể điền kiểu MIME mà phù hợp với bạn). Đây cũng là trường hợp đối với các ứng dụng web dựa trên biểu diễn HTML điển hình (ví dụ: tải trọng json trở nên rất phổ biến để truyền tải trọng cho các yêu cầu ajax).

Về Restful API qua HTTP, các loại nội dung phổ biến nhất mà tôi đã tiếp xúc là application / xml và application / json.

ứng dụng / xml:

  • kích thước dữ liệu: XML rất dài dòng, nhưng thường không phải là vấn đề khi sử dụng nén và nghĩ rằng trường hợp truy cập ghi (ví dụ thông qua POST hoặc PUT) hiếm hơn nhiều so với truy cập đọc (trong nhiều trường hợp, nó chiếm <3% tổng lưu lượng ). Hiếm khi có trường hợp tôi phải tối ưu hóa hiệu suất ghi
  • sự tồn tại của ký tự không phải mã ascii: bạn có thể sử dụng utf-8 làm mã hóa trong XML
  • sự tồn tại của dữ liệu nhị phân: sẽ cần sử dụng mã hóa base64
  • dữ liệu tên tệp: bạn có thể gói gọn trường bên trong này trong XML

ứng dụng / json

  • kích thước dữ liệu: nhỏ gọn hơn XML, văn bản tĩnh, nhưng bạn có thể nén
  • ký tự không phải ascii: json là utf-8
  • dữ liệu nhị phân: base64 (cũng xem json-binary-question )
  • dữ liệu tên tệp: được đóng gói như phần trường riêng bên trong json

dữ liệu nhị phân như tài nguyên riêng

Tôi sẽ cố gắng biểu diễn dữ liệu nhị phân như tài sản / tài nguyên của riêng mình. Nó thêm một cuộc gọi khác nhưng công cụ tách riêng tốt hơn. Hình ảnh ví dụ:

POST /images
Content-type: multipart/mixed; boundary="xxxx" 
... multipart data

201 Created
Location: http://imageserver.org/../foo.jpg  

Trong các tài nguyên sau này, bạn có thể chỉ cần nội tuyến tài nguyên nhị phân dưới dạng liên kết:

<main-resource>
 ...
 <link href="http://imageserver.org/../foo.jpg"/>
</main-resource>

Hấp dẫn. Nhưng khi nào nên sử dụng application / x-www-form-urlencoding và khi đa dữ liệu / biểu mẫu dữ liệu?
tối đa

3
application / x-www-form-urlencoding là loại mime mặc định của yêu cầu của bạn (xem thêm w3.org/TR/html401/interact/forms.html#h-17.13.4 ). Tôi sử dụng nó cho các biểu mẫu web "bình thường". Đối với API tôi sử dụng ứng dụng / xml | json. đa dữ liệu / biểu mẫu dữ liệu là một tiếng chuông trong suy nghĩ của các phần đính kèm (bên trong phần phản hồi, một số phần dữ liệu được kết hợp với một chuỗi ranh giới xác định).
manuel aldana

4
Tôi nghĩ rằng OP có lẽ chỉ hỏi về hai loại biểu mẫu HTML sử dụng, nhưng tôi rất vui vì điều này đã được chỉ ra.
tybro0103

30

Tôi đồng ý với nhiều điều mà Manuel đã nói. Trên thực tế, ý kiến ​​của anh ấy đề cập đến url này ...

http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4

... trong đó nêu rõ:

Loại nội dung "application / x-www-form-urlencoding" không hiệu quả để gửi số lượng lớn dữ liệu nhị phân hoặc văn bản có chứa các ký tự không phải ASCII. Loại nội dung "nhiều dữ liệu / biểu mẫu dữ liệu" nên được sử dụng để gửi biểu mẫu có chứa tệp, dữ liệu không phải ASCII và dữ liệu nhị phân.

Tuy nhiên, đối với tôi nó sẽ được hỗ trợ cho công cụ / khung.

  • Những công cụ và khung nào mà bạn mong đợi người dùng API của mình sẽ xây dựng ứng dụng của họ?
  • Họ có các khung hoặc thành phần mà họ có thể sử dụng theo phương pháp này hơn phương pháp kia không?

Nếu bạn hiểu rõ về người dùng của mình và cách họ sẽ sử dụng API của bạn, thì điều đó sẽ giúp bạn quyết định. Nếu bạn thực hiện tải lên các tệp khó khăn cho người dùng API thì họ sẽ chuyển đi, bạn sẽ dành nhiều thời gian để hỗ trợ họ.

Thứ yếu này sẽ là công cụ hỗ trợ BẠN có để viết API của bạn và việc bạn dễ dàng điều chỉnh một cơ chế tải lên so với cơ chế khác.


1
Xin chào, điều đó có nghĩa là mỗi lần chúng tôi đăng bài lên máy chủ web, chúng tôi phải đề cập đến loại Nội dung để cho máy chủ web biết nó có nên giải mã dữ liệu không? Ngay cả khi chúng tôi tự tạo yêu cầu http, chúng tôi PHẢI đề cập đến loại Nội dung phải không?
GMsoF

2
@GMsoF, Đó là tùy chọn. Xem stackoverflow.com/a/16693884/632951 . Bạn có thể muốn tránh sử dụng loại nội dung khi tạo một yêu cầu cụ thể cho một máy chủ cụ thể để tránh các chi phí chung.
Pacerier

2

Chỉ là một gợi ý nhỏ từ phía tôi để tải lên dữ liệu hình ảnh canvas HTML5:

Tôi đang làm việc trong một dự án cho một cửa hàng in và gặp một số vấn đề do tải hình ảnh lên máy chủ xuất phát từ một canvasyếu tố HTML5 . Tôi đã vật lộn trong ít nhất một giờ và tôi đã không nhận được nó để lưu hình ảnh chính xác trên máy chủ của mình.

Khi tôi đặt contentTypetùy chọn cuộc gọi jQuery ajax của mình, application/x-www-form-urlencodedmọi thứ đã đi đúng hướng và dữ liệu được mã hóa base64 được diễn giải chính xác và được lưu thành công dưới dạng hình ảnh.


Có lẽ điều đó giúp được ai đó!


4
Loại nội dung nào được gửi trước khi bạn thay đổi? Vấn đề này có thể là do máy chủ không hỗ trợ loại nội dung bạn đang gửi.
catorda

1

Nếu bạn cần sử dụng Content-Type = x-www-urlencoding-form thì KHÔNG sử dụng FormDataCollection làm tham số: Trong asp.net Core 2+ FormDataCollection không có các hàm tạo mặc định được yêu cầu bởi Formatters. Sử dụng IFormCollection thay thế:

 public IActionResult Search([FromForm]IFormCollection type)
    {
        return Ok();
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.