HTTP POST请求中如何发送参数?

1722
在HTTP的GET请求中,参数被作为查询字符串发送:
http://example.com/page?parameter=value&also=another
在HTTP的POST请求中,参数不会随着URI一起发送。值在哪里?在请求头中吗?在请求体中吗?它是什么样子的?

5
在HTTP的POST请求中,参数不会与URI一起发送。虽然从理论上讲这是可能的,但请不要使其他人感到困惑。根据规范,POST必须处理不可消除的请求,但您可以使用请求体(通过一个空行与标头分隔)和请求参数。 - Mikhail
10个回答

1449

值以请求主体的形式发送,格式与内容类型规定的格式相同。

通常情况下,内容类型为application/x-www-form-urlencoded,因此请求主体使用与查询字符串相同的格式:

parameter=value&also=another

当您在表单中使用文件上传时,需要使用multipart/form-data编码,它具有不同的格式。这更加复杂,但通常您不需要关心它的外观,因此我不会展示一个例子,但知道它存在可能会很有用。


29
我忘记了文件上传是不同的(+1 / 已接受)。你的答案已经足够了,如果能在multipart/form-data上提供更多信息就更好了。对于那些感兴趣的人,这里有一个关于它的问题。 - Camilo Martin
87
注意:正文与标题之间只有一个空行。 - Gab是好人
2
你已经解释了我们应该在HTTPBody中放置什么,但是在HTTPHeader中我们应该放置/编写什么?它有什么作用? - mfaani
5
@Honey:一个POST请求的HTTP头看起来跟GET请求一样,但是使用的动词是POST,而且会有一个内容类型值(和一个可选的内容长度值)作为请求的内容(正文)。每种类型的请求都有一个头部,有些类型还有一个正文。 - Guffa
5
@KennethWorden 不,这些方法都不能正确发送JSON。不过你可以使用multipart/form-data格式在表单中上传一个JSON文件,或者如果你控制请求的构建,可以将内容类型更改为application/json,并直接将JSON文本粘贴到HTTP正文中。 - Cholthi Paul Ttiopic
显示剩余5条评论

476

简短回答:在POST请求中,值是发送在请求的“主体”中的。对于Web表单,它们最可能使用application/x-www-form-urlencodedmultipart/form-data媒体类型发送。设计用于处理Web请求的编程语言或框架通常会对这些请求进行正确处理,并为您提供轻松访问已解码值的方式(例如,在PHP中的$_REQUEST$_POST,或在Python中的cgi.FieldStorage()flask.request.form)。


现在我们稍微偏离一下话题,这可能有助于理解它们之间的差异 ;)

GETPOST 请求之间的差别主要是语义上的。它们也被“使用”方式不同,这解释了传递值方式的差异。

GET(相关RFC章节

执行 GET 请求时,您向服务器请求一个或一组实体。为了允许客户端过滤结果,它可以使用 URL 的所谓“查询字符串”。查询字符串是 ? 后面的部分。这是 URI语法 的一部分。

因此,从您应用程序代码的角度来看(接收请求的部分),您需要检查 URI 查询部分以访问这些值。

请注意,键和值是URI的一部分。 浏览器可能会对URI长度施加限制。 HTTP标准规定没有限制。 但在撰写本文时,大多数浏览器确实限制了URI(我没有具体值)。 GET请求不应用于向服务器提交新信息。 特别是不要使用较大的文档。 这就是您应该使用POSTPUT的地方。

POST(相关RFC章节

执行POST请求时,客户端实际上正在向远程主机提交一个新的文档。 因此,查询字符串在语义上没有意义。 这就是为什么您无法在应用程序代码中访问它们的原因。

POST有点复杂(并且更加灵活):

当接收到POST请求时,您应该始终期望一个"有效载荷",或者在HTTP术语中称为消息主体。消息主体本身是相当无用的,因为据我所知,没有标准格式(也许是application/octet-stream?)。消息主体的格式由Content-Type头部定义。当使用HTML FORM元素并设置method="POST"时,通常为application/x-www-form-urlencoded。另一个非常常见的类型是multipart/form-data,如果您使用文件上传,则会使用此类型。但它也可能是任何东西,从text/plain,到application/json甚至是自定义的application/octet-stream

无论如何,如果使用了应用程序无法处理的Content-Type请求进行POST,则应返回一个415状态码

大多数编程语言(和/或Web框架)都提供了一种方法来对消息正文进行最常见类型的解/编码(例如application/x-www-form-urlencodedmultipart/form-dataapplication/json)。所以这很容易。自定义类型可能需要更多的工作。

以标准的HTML表单编码文档为例,应用程序应执行以下步骤:

  1. 读取Content-Type字段
  2. 如果该值不是受支持的媒体类型之一,则返回带有415状态代码的响应
  3. 否则,从消息正文中解码值。

再次强调,像PHP这样的语言或其他流行语言的web框架可能会为您处理此问题。唯一的例外是415错误。没有一个框架可以预测您的应用程序选择支持或不支持哪些内容类型。这取决于您自己。

PUT(相关RFC章节)

PUT请求的处理方式与POST请求基本相同。最大的区别在于,POST请求应该让服务器决定如何(如果有必要)创建新资源。历史上(来自现已过时的RFC2616),它是将新资源创建为URI的“下级”(子级)。

一个PUT请求与之相反,它应该将一个资源“存储”在那个URI上,并且使用恰好那个内容。不多也不少。这个想法是客户端负责在“PUTting”之前制作完整的资源。服务器应该接受给定URL上的原样内容。
因此,POST请求通常不用于替换现有资源。一个PUT请求既可以创建也可以替换。

副记

还有"路径参数",可以用于向远程发送附加数据,但它们非常不常见,我不会在这里详细介绍。但是,供参考,以下是RFC的摘录:
除了层次路径中的点段外,通用语法认为路径段是不透明的。URI生成应用程序通常使用段中允许的保留字符来分隔特定于方案或解引用处理程序的子组件。例如,分号(“;”)和等于号(“=”)保留字符通常用于分隔适用于该段的参数和参数值。逗号(“,”)保留字符通常用于类似的目的。例如,一个URI生成器可能使用诸如“name;v=1.1”的段来指示对“name”的版本1.1的引用,而另一个生成器可能使用诸如“name,1.1”的段来表示相同的内容。参数类型可以由特定于方案的语义定义,但在大多数情况下,参数的语法是特定于URI解引用算法的实现的。

1
我可能有点跑题了。我在答案的顶部添加了一个“tl;dr”,这应该会使它更清晰明了。 - exhuma
我刚刚编辑了它,将参考文献从RFC2616更改为RFC7231(已经过时一段时间)。除了更新的链接之外,这个答案的主要区别在于“PUT”部分。 - exhuma
我认为PUT和POST的处理方式不同,因为它应该是幂等的?https://dev59.com/rXRB5IYBdhLWcg3weHLx - rogerdpack
2
@rogerdpack 你说得没错。如果你读一下“PUT”章节中的第二段,你会发现它 确实 是幂等的。相比之下,“POST”是无法做到的 - 根据定义。POST将始终创建一个新资源。 如果已经存在一个完全相同的资源,则PUT将替换它。因此,如果调用 POST 10次,则会创建10个资源。如果调用PUT 10次,则只会(也许)创建一个。 这回答了您的问题吗? - exhuma

471

HTTP头部之后是内容。 HTTP POST的格式为:先是HTTP头部,接着是一个空行,再接着是请求正文。POST变量以键值对的形式存储在正文中。

您可以在下面显示的HTTP Post的原始内容中看到这一点:

POST /path/script.cgi HTTP/1.0
From: frog@jmarshall.com
User-Agent: HTTPTool/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 32

home=Cosby&favorite+flavor=flies
你可以使用像Fiddler这样的工具来查看原始的HTTP请求和响应负载数据在网络上发送的过程。

51
只有当内容类型为 application/x-www-form-urlencoded 时才成立,而这并非总是如此。 - Guffa
@ Camilo Martin .... [+1] 对于好问题的赞 & @ Joe Alfano .... [+1] 对于好答案的赞 ....... 我现在对于POST请求有了清晰的理解 .... 但是如果一张图片和键值对数据信息一起发送 ..... POST请求的结构会是什么样子呢? - Devrath
12
@Joe,现在你为什么要放一个“From”标头呢? - Pacerier
2
@Joe,我喜欢随机包含From头信息。在我看来,它与HTTP 418状态码一样重要。 - Tom Howard
你如何添加用户和密码认证? - m4l490n

66

你不能直接在浏览器的URL栏中输入它。

例如,使用Live HTTP Headers,您可以查看POST数据如何通过互联网发送。结果可能是这样的:

http://127.0.0.1/pass.php
POST /pass.php HTTP/1.1

Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://127.0.0.1/pass.php
Cookie: passx=87e8af376bc9d9bfec2c7c0193e6af70; PHPSESSID=l9hk7mfh0ppqecg8gialak6gt5
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
username=zurfyx&pass=password

在它所说的地方

Content-Length: 30
    username=zurfyx&pass=password

将是提交的数值。


3
澄清一下:这里的“Content-Length”是否应该是“29”?这是字符串“username=zurfyx&pass=password”的实际长度。 - Hippo
1
@Hippo那个换行符应该在那里吗? - vikingsteve
@vikingsteve 我知道你的意思。那么我想内容末尾总是有换行符的。 - Hippo
4
标题和正文之间需要额外换行来分隔。 - Mára Toner

30

POST请求的默认媒体类型是application/x-www-form-urlencoded,这是一种用于编码键-值对的格式。键可以重复。每个键-值对由&字符分隔,每个键与其值之间由=字符分隔。

例如:

Name: John Smith
Grade: 19

被编码为:

Name=John+Smith&Grade=19

这是放置在HTTP头之后的请求主体。


2
你已经解释了我们应该在HTTPBody中放置什么,但是我们应该在HTTPHeader中放置/编写什么? - mfaani
你提到了键可以重复,那么这样的重复会有什么结果呢?最后一个键值会自动覆盖之前的值吗?谢谢。 - Jinghui Niu
@JinghuiNiu 如果键重复,应将其解析为数组。 虽然很晚了,但可能会对其他人有帮助。 - Hanash Yaslem

21

HTTP POSTs中的表单值被发送到请求正文中,格式与查询字符串相同。

更多信息请参见规范


8
“Same format”有点含糊不清。比如说,它们是否以?开头? - Camilo Martin
9
@PeterWooster 是的,但未提供示例。在这方面,就像是一个回答说“看,应用程序博客中有你问题的答案(链接)”。 - Camilo Martin
40
@PeterWooster 不是必需的,但当你忘记某些东西时,去谷歌搜索并进入第一个是SO的链接非常好,那里有一个清晰简洁的示例,告诉你需要什么,而不是让你去消化过于详细的规范,即使这些规范全面,也可能不适合复习。想一想:这个网站上大部分的问答都可以归结为“去阅读规范/手册/API等 *(链接)*”。这有用吗?不比谷歌更有用。 - Camilo Martin
3
只有当内容类型为“application/x-www-form-urlencoded”时才成立,但这并不总是情况如此。 - Guffa
4
GET查询字符串的格式与application/x-www-form-urlencoded格式不同。例如,空格被编码成不同的形式(%20 和 +)。在这方面,回答是误导人的。 - ᄂ ᄀ
显示剩余4条评论

20

有些网络服务要求您将请求中的数据元数据分开放置。例如,远程函数可能希望在URI中包含已签名的元数据字符串,而数据则通过HTTP正文进行发布。

POST请求在语义上可能类似于:

POST /?AuthId=YOURKEY&Action=WebServiceAction&Signature=rcLXfkPldrYm04 HTTP/1.1
Content-Type: text/tab-separated-values; charset=iso-8859-1
Content-Length: []
Host: webservices.domain.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)

name    id
John    G12N
Sarah   J87M
Bob     N33Y

这种方法通过使用单个Content-Type逻辑地将QueryString和Body-Post结合在一起,Content-Type是Web服务器的“解析指令”。

请注意: HTTP/1.1左侧被#32(空格)包裹,右侧被#10(换行符)包裹。


/user/john/?user=john 之间的区别仅仅是语义上的不同(HTTP 并没有对查询字符串进行特殊处理),所以我认为这是可以理解的。但是您所说的“左侧被空格包裹”是什么意思?在 HTTP 方法前面并没有空格。您是说用于 POST 请求正文的空行吗? - Camilo Martin
1
在上述代码中,...Ym04HTTP/1.1之间有一个空格(ASCII#32)。因此,查询字符串只是位于动词和协议版本之间。 - Interface Unknown
1
你的备注让它听起来像是一些意外和版本特定的问题。坦白地说,看起来很明显那里有一个空格。并且换行符也适用于其他行,就像UNIX的所有东西一样。 - Camilo Martin
2
我只是强调了代码中无法标记的部分。这可能看起来很明显,但有时并不是这样。 - Interface Unknown
确实,我们可以通过在URI和参数之间使用“?”来将查询参数作为URL的一部分传递,就像我们在GET请求中所做的那样。 - asgs

13

首先,让我们区分 GETPOST

GET:它是向服务器发出的默认的 HTTP 请求,用于从服务器检索数据,并使用跟随在 URI? 后面的查询字符串来检索唯一的资源。

这是格式。

GET /someweb.asp?data=value HTTP/1.0

在这里,data=value 是传递的查询字符串值。

POST:它用于安全地向服务器发送数据,因此任何需要的内容都可以使用这种格式的POST请求。

POST /somweb.aspHTTP/1.0
Host: localhost
Content-Type: application/x-www-form-urlencoded //you can put any format here
Content-Length: 11 //it depends
Name= somename

为什么要使用POST而不是GET?

GET 中,值通常会附加到基本URL的查询字符串中发送到服务器,这会产生两个后果:

  • GET 请求会保存在浏览器历史记录中,并带有参数。因此,您的密码将在浏览器历史记录中保持未加密状态。这是 Facebook 在过去真正面临的问题。
  • 通常,服务器对URI的长度有限制。如果发送太多参数,则可能会收到 414错误 - URI过长

在进行 POST 请求时,表单数据将添加到请求的主体中。请求参数的长度计算并添加到标头中作为内容长度,没有重要数据直接附加到 URL 中。

您可以使用 Google 开发者工具的网络部分查看关于如何向服务器发出请求的基本信息。

您始终可以在请求标头中增加更多值,例如Cache-ControlOriginAccept


5
关于安全性的假设只在HTTPS连接的情况下才是正确的,而不是HTTP。 当HTTP既不加密也不保护时,HTTPS会同时加密URL(包括查询参数)和请求体。 所描述的问题源于许多浏览器将URI(包括URL)存储在其历史数据库中(通常不加密)。 因此,在处理任何敏感信息时,请仅使用请求体+HTTPS - Petru Zaharia
@PetruZaharia 我同意你的解释。你也可以建议这个编辑,我很乐意接受! :) - Zeeshan Adil

2

有许多种/格式的提交参数

  • 表单数据
  • 原始数据
  • JSON
  • 编码数据
  • 文件
  • XML

它们由Header中的content-type控制,表示为mime类型。


2
你的回答如何扩展或澄清此问题的其他答案? - Bob Dalgleish

0
在《全球网络CGI编程》一书中,作者说:

使用POST方法,服务器将数据作为输入流发送到程序中。......由于服务器将信息作为输入流传递给该程序,因此它设置环境变量CONTENT_LENGTH以字节(或字符)的数量表示数据的大小。我们可以使用这个值从标准输入中精确地读取相应数量的数据。


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