在工作中,熟悉常见的Content-Type类型对于处理POST请求至关重要,它可以帮助我们更好地使用和解决问题。接下来我们一起学习POST请求相关的知识要点。
介绍
POST请求是一种常见的数据请求方式,相对于 GET 请求更安全、更灵活。一个标准的 POST 请求由以下三个部分组成:
- 请求行:包含了请求方法、URL和HTTP协议版本。
- 请求头:包含了关于请求的附加信息,常见的请求头字段有
Content-Type
、Authorization
等。 - 请求主体:请求的数据存储在请求主体中,具体的数据格式和编码方式由
Content-Type
字段确定。服务端会根据 Content-Type 字段使用不同的方式对请求体进行解析。
常见的 Content-Type
application/x-www-form-urlencoded
这是POST请求中最常见的一种编码方式,适用于表单数据提交。它是原生表单 POST 提交(enctype)的默认值,大部分服务端语言都对这种方式有很好的支持。
当使用 application/x-www-form-urlencoded
提交数据时,需要对参数进行 urlencoded
编码和序列化。数据被编码成以 & 分隔的键值对,同时以 = 分隔键和值,非字母或数字的字符会被 percent-encoding(百分比编码)。
例如,表单提交参数为:
makefile
param1:website,
param2:https://www.google.com
经过 urlencoded
编码后:
makefile
param1:website
param2:https%3A%2F%2Fwww.google.com
再经过序列化,得到结果:
ini
param1=website¶m2=https%3A%2F%2Fwww.google.com
采用这种格式发送到服务器的 HTTP 消息的主体本质上是一个巨大的查询字符串,结构简单。
但是由于需要对数据进行编码,这意味着值中存在的每个非字母数字,将需要三个字节来表示它。对于大型二进制文件,请求体将增加三倍,非常低效。
另外由于对数据进行了编码,非 ASCII 码的数据会丢失,所以类似传文件的场景也不能使用这种方式。
适用场景:数据量小的简单数据
multipart/form-data
这种编码方式主要用于文件上传,可以有效地传输二进制(非字母数字)数据,同时还可以携带其他信息。
在 multipart/form-data
格式中,数据被划分为多个部分(part),每个部分都以一个标头(headers)开始,并使用一组空行 来分隔。同时,每个部分还包含一个唯一的 boundary
字符串,用于标识不同部分之间的边界。
每个部分包含以下组成:
- 边界:
--boundary
- 标头:元数据,如
Content-Type
、Content-Disposition
等。 - 空行:用于分隔标头和正文数据。
- 正文数据:包含实际的文件数据或表单字段的值。
最后以 --boundary--
表示请求体结束
一个简单的例子如下:
css
POST http://www.example.com/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="username"
zhang san
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="avatar"; filename="avatar.png"
Content-Type: image/png
...二进制数据...
------WebKitFormBoundaryABC123--
在这个例子中,请求头中指定了 Content-Type: multipart/form-data
,并设置了一个唯一的 boundary
字符串作为分隔符(在此例中为 ----WebKitFormBoundaryABC123
)。
在请求体中,我们可以看到两个部分。
-
第一部分:以
--boundary
开始,并包含了一个标头Content-Disposition: form-data; name="username"
。这表示这个部分是一个表单字段,字段名为username
,字段值为zhang san
。 -
第二部分:以
--boundary
开始,并包含了两个标头。首先是Content-Disposition: form-data; name="avatar"; filename="avatar.png"
。然后是Content-Type: image/png
,指定了这个部分的数据类型为image/png
。接着是二进制文件数据主体。
最后以 --boundary--
表示结束。
multipart/form-data
也可以用于传递普通数据,但是由于每个字段都有自己的一组MIME标头和边界字符串,这会增加数据包的大小。相对于其他简单的编码方式(如 application/x-www-form-urlencoded
),它在处理普通数据时效率低,开销更大。
适用场景:文件上传、带有二进制数据的请求
application/json
使用 application/json
编码方式,可以将数据序列化成一个JSON字符串作为请求主体。相比其他只能传输键值对的方式,这种编码方式更加灵活和简单高效。它支持复杂的嵌套结构、数组、对象等,非常适合传输包含多层级数据关系的数据。
bash
POST http://www.example.com HTTP/1.1
Content-Type: application/json;charset=utf-8
{"name":"test", "age": 24, "hobby":["a","b","c"]}
适用场景:复杂的结构化的数据
使用 Axios 发送 POST 请求
当我们工作中使用 axios 发送请求时,axios 会对请求头和请求参数进行一些额外的处理,方便我们使用。接下来我们通过源码来进行一个简单的分析。
首先我们找到源码中关于请求头中 Content-Type
处理的代码(不同版本代码略有不同):
js
// https://github.com/axios/axios/blob/v2.x/lib/defaults/index.js
var DEFAULT_CONTENT_TYPE = {
'Content-Type': 'application/x-www-form-urlencoded'
};
// 使用该方法会判定果用户有没有设置 Content-Type
// 没有则会设置相应的类型值
function setContentTypeIfUnset(headers, value) {
if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
headers['Content-Type'] = value;
}
}
function transformRequest(data, headers) {
var contentType = headers && headers['Content-Type'] || '';
var hasJSONContentType = contentType.indexOf('application/json') > -1;
var isObjectPayload = utils.isObject(data);
// 如果参数是个对象并且是 HTML Form 标签,会将参数转化为 FormData 对象
// 补充:如果 FormData 构造函数的参数是一个 DOM 的表单元素,构造函数会自动处理表单的键值对,
// 将 HTML 表单直接读取为 FormData
if (isObjectPayload && utils.isHTMLForm(data)) {
data = new FormData(data);
}
var isFormData = utils.isFormData(data);
if (isFormData) {
if (!hasJSONContentType) {
return data;
}
// 如果参数是 FormData 格式
// 并且用户设置了 Content-Type 是 application/json 格式,会将参数转化为 JSON 字符串
// 否则不处理
return hasJSONContentType ? JSON.stringify(formDataToJSON(data)) : data;
}
// 如果请求参数是 ArrayBuffer、Buffer、Stream、File、Blob 这几种格式的不做任何处理
if (
utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
// 如果请求参数是 ArrayBuffer 格式的则返回 data.buffer
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
// 如果请求参数是一个 URLSearchParams 对象
// 会设置 Content-Type 为 application/x-www-form-urlencoded
// 并调用 URLSearchParams.toString() 方法
// 将参数转为序列化字符串 'foo=bar&hello=world'
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
var isFileList;
// 如果参数是对象类型
if (isObjectPayload) {
// 如果用户设置的 Content-Type 是 application/x-www-form-urlencoded 格式
// 则对参数进行URL编码并序列化 'foo=bar&hello=world'
if (contentType.indexOf('application/x-www-form-urlencoded') !== -1) {
return toURLEncodedForm(data, this.formSerializer).toString();
}
// 如果参数是 一个 FileList 对象
// 或者用户设置了 Content-Type 是 multipart/form-data
// 那么会将参数对象转为 FormData 对象
if ((isFileList = utils.isFileList(data)) || contentType.indexOf('multipart/form-data') > -1) {
var _FormData = this.env && this.env.FormData;
return toFormData(
isFileList ? {'files[]': data} : data,
_FormData && new _FormData(),
this.formSerializer
);
}
}
// 如果参数是对象类型
// 或者设置的 Content-Type 是 application/json
// 会设置 Content-Type 为 application/json
// 并将参数转化为 一个 JSON 字符串
if (isObjectPayload || hasJSONContentType ) {
setContentTypeIfUnset(headers, 'application/json');
return stringifySafely(data);
}
return data;
}
// 如果用户没有设置请求头,同时也没有命中 transformRequest 中对 Content-Type 的处理
// 则会使用默认值 DEFAULT_CONTENT_TYPE
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});
如果请求参数是 FormData 类型的,在最终发送请求前(在 dispatchRequest 里,在拦截器 request 之后),还会进行一次判断处理
js
// axios/lib/adapters/xhr.js
module.exports = function xhrAdapter(config) {
// ...... 已省略部分代码 ......
return new Promise(function dispatchXhrRequest(resolve, reject) {
// 发送ajax之前,如果发现请求参数是 FormData 类型的
// 会删除请求头中 'Content-Type' 的设置,让浏览器自己选择
// multipart/form-data
if (utils.isFormData(requestData) && utils.isStandardBrowserEnv()) {
delete requestHeaders['Content-Type']; // Let the browser set it
}
})
}
当 axios 使用 application/x-www-form-urlencoded 请求时,如果参数不是 URLSearchParams 类型的,则可能需要我们使用 qs.stringify 方法对参数进行序列化。
总结
本文详细介绍了与POST请求相关的Content-Type类型。通过了解这些Content-Type类型可以在开发过程中快速的排查并解决相关问题,帮助我们提高开发效率。
往期文章推荐: