enctype='multipart/form-data' 是什么意思?

1863
在HTML表单中,enctype='multipart/form-data'是指表单数据将会以二进制形式进行编码并且传输文件。当表单需要上传文件时,应该使用这个属性。

5
本文描述了HTML表单元素与控件之间的关联,并介绍了用于指定表单数据编码类型的enctype属性及其取值。该属性用于指示服务器在接收表单提交数据时如何解析数据并进行处理。enctype属性的取值包括application/x-www-form-urlencoded(默认值)、multipart/form-data和text/plain三种。其中,application/x-www-form-urlencoded适用于大多数情况,multipart/form-data适用于上传文件,而text/plain则很少使用。 - IEnjoyEatingVegetables
2
这是用于作为表单提交的一部分POST整个文件。我以为它可能与多个文本表单输入部分有关,但实际上不是,它只是用于文件上传。 - Janac Meena
9个回答

2024
当你发起一个POST请求时,必须以某种方式编码构成请求主体的数据。
HTML表单提供了三种编码方法
  • application/x-www-form-urlencoded (默认值)
  • multipart/form-data
  • text/plain
正在进行添加application/json的工作,但现在已经被放弃了。
(使用其他方式生成的HTTP请求可能具有其他编码。 JSON是用于Web服务的常见格式,一些人仍然使用SOAP。)
对于大多数开发者来说,格式的细节并不重要。 重要的是:
  • 永远不要使用text/plain
当你编写客户端代码时:
  • 如果你的表单包含任何元素,请使用multipart/form-data
  • 否则,你可以使用multipart/form-dataapplication/x-www-form-urlencoded,但application/x-www-form-urlencoded更加高效
当你编写服务器端代码时:
  • 使用预先编写的表单处理库
大多数编程语言(例如Perl的CGI->param或PHP的$_POST超全局变量)会为您处理这些差异。不要尝试解析服务器接收到的原始输入。

有时,您可能会发现某个库无法同时处理两种格式。Node.js最受欢迎的处理表单数据的库是body-parser,它无法处理多部分请求(但有文档推荐一些替代方案)。


如果您正在编写(或调试)用于解析或生成原始数据的库,那么您需要开始关注格式。出于兴趣,您可能还想了解它。application/x-www-form-urlencoded与URL末尾的查询字符串几乎相同。multipart/form-data要复杂得多,但它允许将整个文件包含在数据中。结果的示例可以在HTML 4规范中找到。text/plain由HTML 5引入,仅用于调试 - 来自规范它们不能被计算机可靠地解释 - 我认为结合工具(如大多数浏览器的网络面板中的开发人员工具)的其他方法更好。

8
@Quentin 对不起,请问如果我们在所有表单中都使用multipart,会有什么可能的问题?包括带文件和不带文件的表单。 - Positivity
25
对于GET表单来说这没有意义,而且它会增加请求的文件大小。 - Quentin
3
HTML表单提供三种编码方式。 - Quentin
7
因为它有多个部分。 - Quentin
2
@MasterJoe 因为它可以有多个由边界分隔的数据项,请参见RFC 2046第5.1.1节 - Ruslan
显示剩余3条评论

635
什么时候应该使用它? Quentin's answer 是正确的:如果表单包含文件上传,请使用 multipart/form-data,否则默认使用 application/x-www-form-urlencoded,如果省略了 enctype
我将:
  • 添加更多的 HTML5 参考
  • 通过一个表单提交示例来解释他为什么是正确的

HTML5 参考

enctype三种可能性

如何生成示例

一旦您看到每种方法的示例,就会明显地了解它们的工作原理以及何时应该使用它们。

您可以使用以下方法生成示例:

将表单保存为最小的 .html 文件:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
  <title>upload</title>
</head>
<body>
<form action="http://localhost:8000" method="post" enctype="multipart/form-data">
  <p><input type="text" name="text1" value="text default">
  <p><input type="text" name="text2" value="a&#x03C9;b">
  <p><input type="file" name="file1">
  <p><input type="file" name="file2">
  <p><input type="file" name="file3">
  <p><button type="submit">Submit</button>
</form>
</body>
</html>

我们将默认文本值设置为 a&#x03C9;b,意思是 aωb,因为 ωU+03C9,在 UTF-8 中对应的字节是 61 CF 89 62
创建要上传的文件:
echo 'Content of a.txt.' > a.txt

echo '<!DOCTYPE html><title>Content of a.html.</title>' > a.html

# Binary file containing 4 bytes: 'a', 1, 2 and 'b'.
printf 'a\xCF\x89b' > binary

运行我们的小型回声服务器:
while true; do printf '' | nc -l localhost 8000; done

在您的浏览器中打开HTML文件,选择文件并单击提交,然后检查终端。 nc 打印收到的请求。
测试环境:Ubuntu 14.04.3,nc BSD 1.105,Firefox 40。

multipart/form-data

Firefox发送了:
POST / HTTP/1.1
[[ Less interesting headers ... ]]
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

<!DOCTYPE html><title>Content of a.html.</title>

-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file3"; filename="binary"
Content-Type: application/octet-stream

aωb
-----------------------------735323031399963166993862150--

对于二进制文件和文本字段,字节 61 CF 89 62(UTF-8 编码下的 aωb)直接发送。您可以使用 nc -l localhost 8000 | hd 命令进行验证,该命令会显示以下字节:
61 CF 89 62

被发送的内容为 (61 == 'a' and 62 == 'b')。
因此可以清楚地得出:
  • Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150 sets the content type to multipart/form-data and says that the fields are separated by the given boundary string.

    But note that the:

    boundary=---------------------------735323031399963166993862150
    

    has two less dashes -- than the actual barrier

    -----------------------------735323031399963166993862150
    

    This is because the standard requires the boundary to start with two dashes --. The other dashes appear to be just how Firefox chose to implement the arbitrary boundary. RFC 7578 clearly mentions that those two leading dashes -- are required:

    4.1. "Boundary" Parameter of multipart/form-data

    As with other multipart types, the parts are delimited with a boundary delimiter, constructed using CRLF, "--", and the value of the "boundary" parameter.

  • every field gets some sub headers before its data: Content-Disposition: form-data;, the field name, the filename, followed by the data.

    The server reads the data until the next boundary string. The browser must choose a boundary that will not appear in any of the fields, so this is why the boundary may vary between requests.

    Because we have the unique boundary, no encoding of the data is necessary: binary data is sent as is.

    TODO: what is the optimal boundary size (log(N) I bet), and name / running time of the algorithm that finds it? Asked at: https://cs.stackexchange.com/questions/39687/find-the-shortest-sequence-that-is-not-a-sub-sequence-of-a-set-of-sequences

  • Content-Type is automatically determined by the browser.

    How it is determined exactly was asked at: How is mime type of an uploaded file determined by browser?

application/x-www-form-urlencoded

现在将 enctype 改为 application/x-www-form-urlencoded,重新加载浏览器并重新提交。

Firefox 发送了:

POST / HTTP/1.1
[[ Less interesting headers ... ]]
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

显然,文件数据并未发送,只有基本文件名。因此,这不能用于文件。
至于文本字段,我们可以看到通常的可打印字符(如ab)占用一个字节,而不可打印字符(如0xCF0x89)每个占用3个字节%CF%89
比较
文件上传通常包含大量的不可打印字符(例如图像),而文本表单几乎不包含不可打印字符。
从我们所看到的例子来看:
- multipart/form-data:将一些字节作为消息的边界开销添加到消息中,并花费一些时间计算它,但是将每个字节都作为一个字节发送。 - application/x-www-form-urlencoded:每个字段有一个字节境界(&),但对于每个不可打印字符,增加了3倍的线性开销。
因此,即使我们可以使用“application/x-www-form-urlencoded”发送文件,也不希望这样做,因为它效率低下。
但对于在文本字段中找到的可打印字符,这并不重要,并且会生成较少的开销,因此我们只需使用它。

3
@Khanna111%CF这个字符由三个字节构成:%CF :-) 下面是它变得容易识别的故事。 - Ciro Santilli OurBigBook.com
10
在OS X上,nc命令不支持同时使用-l-p参数。但是以下命令可以正常运行:while true; do printf '' | nc -l 8000; done - PhilipS
1
据我所知,添加任何破折号到边界的目的是为了使得通过肉眼检查请求语法变得不可能。请不要在您的边界标记中使用它们。 - Dewi Morgan
1
@DewiMorgan 你说得完全正确。我编辑了帖子并从边界字符串中删除了破折号。 - Max
1
非常有信息量的帖子。对于那些想要复制一切的人,我有一个小观察:我认为netcat命令的参数是反过来的,主机应该在端口之前(就像参考帖子中一样),所以在这种情况下,命令应该是nc -l localhost 8000。否则,你会得到一个nc: getaddrinfo: Servname not supported for ai_socktype错误。 - F.Webber
显示剩余15条评论

111

enctype='multipart/form-data' 是一种编码类型,允许文件通过 POST 方法传输。没有这个编码类型,文件将无法通过 POST 方法发送。

如果您想通过表单允许用户上传文件,必须使用此 enctype


那么,如果文件不是二进制文件,我们可以不用它工作吗? - Yugal Jindle
据我所知,您可以使用 multipart/form-data 发送非二进制文件,但这是低效的。我认为使用 application/x-www-form-urlencoded 是发送非二进制数据的正确方式,但是有更多经验处理非二进制文件的人可能需要纠正我。 - Matt Asbury
16
使用multipart/form-data发送文件的主要优势是它可以自动在前端和后端工作,您无需进行任何特殊处理。所有文件都是二进制的,即使它们只包含文本。application/x-www-form-urlencoded是在不附带文件的情况下提交表单的标准方式。而multipart/form-data是在附带文件的情况下提交表单的标准方式。(还有许多其他编码方式,例如application/jsonapplication/json-patch+json,这些常用于服务器和客户端之间的通信。) - Daniel Luna
9
值得注意的是,您可以将图像进行Base64编码,并将其作为纯字符串数据发送。 - Tegra Detra
12
关于@Prospero上面的评论:你完全可以在不使用multipart/form-data的情况下通过POST方式发送文件。但是,如果没有使用JavaScript,在普通的HTML表单提交中无法这样做。将表单设置为使用multipart/form-data是* HTML 提供的唯一机制,让您在不使用JavaScript的情况下POST文件。我觉得答案中这个问题不够清晰,一个不熟悉的读者可能会认为不能在没有multipart/form-data的情况下发送文件是HTTP*的限制;这并不是真的。 - Mark Amery

96

当提交一个表单时,你告诉浏览器通过HTTP协议在网络上传送一条消息,并将其妥善地封装在TCP/IP协议消息结构中。HTML页面有一种方法可以向服务器发送数据:使用<form>表单。

当表单被提交时,会创建并发送一个HTTP请求到服务器,该消息将包含表单中的字段名称和用户填写的值。这个传输过程可以通过POSTGET HTTP方法来完成。

  • POST 告诉浏览器构建一个HTTP消息,并将所有内容放在消息的正文中(这是一种非常有用、更安全、更灵活的做法)。
  • GET 将在查询字符串中提交表单数据。它对于数据表示和长度有一些限制。

说明如何将表单发送到服务器

只有在使用POST方法时,属性enctype才有意义。当指定时,它指示浏览器以特定的方式编码其内容来发送表单。从MDN - 表单enctype中得知:

当 method 属性的值为 post 时,enctype 是用于将表单提交到服务器的内容类型的 MIME 类型。

  • application/x-www-form-urlencoded:这是默认值。当表单被发送时,所有名称和值都将被收集并对最终字符串执行URL编码
  • multipart/form-data:字符不进行编码。这在表单具有文件上传控件时非常重要。您希望发送二进制文件,并确保比特流不被更改。
  • text/plain:空格会被转换,但不会执行更多编码。
  • 安全性

    提交表单时,可能会出现一些安全问题,如RFC 7578第7节中所述的多部分表单数据 - 安全注意事项所述:

    所有处理表单的软件都应该对用户提供的表单数据进行敏感处理,因为它通常包含机密或个人身份信息。Web浏览器中广泛使用表单“自动填充”功能;这些功能可能被用来欺骗用户在完成其他无害任务时无意中发送机密信息。multipart / form-data不提供任何检查完整性、确保保密性、避免用户混淆或其他安全功能的功能;那些关注必须由填写表单和解释表单数据的应用程序解决。

    接收表单并处理它们的应用程序必须小心,不能向请求表单处理站点提供未经意图发送的数据。

    在解释Content-Disposition头字段的文件名时,重要的是不要在接收者的文件空间中不经意地覆盖文件。

    如果您是开发人员,并且您的服务器将处理用户提交的可能包含敏感信息的表单,则这会涉及到您。


    4
    最近编辑后关于安全性的内容与“enctype”所做的事情无关。我知道这些内容字面上来自“multipart/form-data” RFC,但它们是有关提交表单的安全注意事项的任意转储,这些注意事项完全与数据是作为“application/x-www-form-urlencoded”还是“multipart/form-data”发送无关。 - Mark Amery

    53

    enctype='multipart/form-data' 意味着不会对任何字符进行编码。因此,在上传文件到服务器时,使用这种类型。
    因此,当表单需要上传二进制数据(如文件内容)时,使用 multipart/form-data


    11

    将method属性设置为POST,因为使用表单无法将文件内容放入URL参数中。

    将enctype的值设置为multipart/form-data,因为数据将被分割成多个部分,一个用于每个文件,另一个用于可能与它们一起发送的表单主体文本。


    这意味着通过表单提交文件时,POST 可能已经足够了,并且以某种模糊的方式添加 multipart/form-data 只是额外奖励。但事实并非如此。大多数文件绝对需要使用 multipart/form-data - underscore_d

    11
    • enctype编码类型)属性指定在将表单数据提交到服务器时应如何对其进行编码。
    • multipart/form-dataenctype 属性的值之一,用于具有文件上传的表单元素。 多部分 意味着表单数据被分成 多个部分 并发送到服务器。

    10
    我认为 enctype 不代表加密类型。在这个层次上没有涉及到加密。我猜测它可能是编码类型或者封闭类型。但肯定不是加密类型。 - Yeo
    1
    你在这里关于 <head><body> 的最后一个要点是无关紧要且令人困惑的。 - Mark Amery

    3

    通常情况下,当您有一个POST表单需要以文件上传为数据时...这将告诉服务器它将如何转码传输的数据,在这种情况下,它不会被编码,因为它只会将文件传输和上传到服务器,例如,上传图像或PDF文件。


    -3
    enctype 属性指定在将表单数据提交到服务器时应如何对其进行编码。
    只有在 method="post" 时才能使用 enctype 属性。
    不对任何字符进行编码。当您使用具有文件上传控件的表单时,需要此值。
    来自 W3Schools

    3
    这句话甚至没有提到 multipart/form-data。而且意思也不太清楚;“没有字符被编码”这句话是什么意思?-1。 - Mark Amery

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接