赞
踩
在前端开发中,我们经常需要与后端进行数据交互。然而,在发送网络请求时,很多开发者可能会遇到一个共同的问题:如何正确地设置请求头中的Content-Type以及后端如何处理接收到的数据。与数据类型相关的请求/响应头主要是两个:Accept和Content-Type,其中Content-Type在请求头和响应头中都存在。我们从请求头的角度来介绍各个数据类型。
本博客将深入解析Content-Type的含义,介绍它们的分类和原因,并从前端和后端两个角度分别说明如何使用和处理。
Content-Type头部是在客户端向服务器发送请求时,指定请求体的媒体类型。服务器据此判断请求体的格式,从而正确解析数据。
Content-Type的分类是基于MIME类型的,MIME类型通常由两部分组成,一部分是媒体类型(media type),另一部分是子类型(subtype),用斜杠分隔。例如,text/html
表示数据的大类是文本,子类是HTML。
MIME 类型有很多种,不同的应用场景可能会使用不同的 MIME 类型。
一般来说,常见的 MIME 类型有以下几类:
text:表示纯文本或者文本格式的数据,如 text/plain, text/html, text/css, text/xml 等。
image:表示图像或者图形格式的数据,如 image/jpeg, image/png, image/gif, image/svg+xml 等。
audio:表示音频或者声音格式的数据,如 audio/mpeg, audio/wav, audio/ogg 等。
video:表示视频或者动画格式的数据,如 video/mp4, video/webm, video/ogg 等。
application:表示其他类型的数据,通常是二进制格式或者特定应用程序的格式,如 application/pdf, application/zip, application/json, application/javascript 等。
multipart:表示多个部分组成的数据,每个部分可以有自己的 MIME 类型,如 multipart/form-data, multipart/mixed 等。
message:表示电子邮件或者其他消息格式的数据,如 message/rfc822, message/http 等。
这是一种用于发送表单数据的类型,它会将数据以键值对的形式编码,例如name=Tom&age=18
。键和值都会进行URL编码,以避免特殊字符的影响。
这种类型的优点是可以发送任何类型的字符,但缺点是不能发送二进制数据,如文件上传,而且URL编码会增加数据的长度,可能会超过服务器的限制。
这是默认的表单编码类型,它会将表单中的数据经过URL编码后,用&符号分隔,发送到服务器。
例如,如果表单中有两个字段,fname=张,lname=san,html内容为:
<form
action="http://localhost:3000/test"
method="post"
enctype="application/x-www-form-urlencoded"
>
First name: <input type="text" name="fname" /><br />
Last name: <input type="text" name="lname" /><br />
<input type="submit" value="Submit" />
</form>
那么发送的数据就是:
fname=%E5%BC%A0&lname=san
总的来说这种方式使用fetch比较少:
我们在请求体中传入编码后的字符串(在body
字段中添加形如name=Tom&age=18
的数据即可),具体来说有这几种方式。
这种方式其实不好,因为可能会对某些字符忽略了url编码
fetch('https://example.com/api', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'name=Tom&age=18'
})
.then(response => response.json())
.then(data => console.log(data));
手动拼接之前为合法字符串,首先进行url的编码才行
var details = { 'userName': 'test@gmail.com', 'password': 'Password!', 'grant_type': 'password' }; var formBody = []; for (var property in details) { var encodedKey = encodeURIComponent(property); var encodedValue = encodeURIComponent(details[property]); formBody.push(encodedKey + "=" + encodedValue); } formBody = formBody.join("&"); fetch('https://example.com/login', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' }, body: formBody })
URLSearchParams
统一的处理const formData = new URLSearchParams({ username: "test@gmail.com", password: "Password", grant_type: "password", }); // 发送Fetch请求 fetch("https://example.com/api/login1", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: formData.toString(), }) .then((response) => response.json()) .then((data) => console.log(data)) .catch((error) => console.error("Error:", error))
// 后端使用express接收application/x-www-form-urlencoded类型的数据
const express = require('express');
const app = express();
// 使用body-parser中间件的urlencoded方法来解析请求体
app.use(express.urlencoded({extended: false}));
app.post('/api', (req, res) => {
// req.body是一个对象,其中每个键值对对应一个表单字段和值
console.log(req.body); // {name: 'Tom', age: '18'}
})
这是一种用于发送JSON数据的类型,它会将数据以JSON字符串的形式编码,例如{"name":"Tom","age":18}
。
使用起来其实和www-form-urlencoded
类似,感觉更加的方便一些。
使用JSON.stringify
转一下即可
// 前端使用fetch发送application/json类型的数据
fetch('https://example.com/api', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({name: 'Tom', age: 18})
})
.then(response => response.json())
.then(data => console.log(data));
// 后端使用express接收application/json类型的数据
const express = require('express');
const app = express();
// 使用epress自带的中间件的json方法来解析请求体
app.use(express.json());
app.post('/api', (req, res) => {
// req.body是一个对象,其中每个键值对对应一个JSON字段和值
console.log(req.body); // {name: 'Tom', age: 18}
})
这是一种用于上传文件的类型,它会将数据以多部分的形式编码,每个部分都有一个边界和一个内容类型。这种类型用于需要发送二进制数据的情况,如文件上传。
这种类型的优点是可以发送任何类型的数据,包括二进制数据,但缺点是数据的格式比较复杂,需要额外的分隔符和描述信息,可能会增加数据的开销。
它会将表单中的数据分成多个部分,每个部分都有一个分隔符和一个描述信息,发送到服务器。
例如表单代码为:
<form
action="http://localhost:3000/files"
method="post"
enctype="multipart/form-data"
>
姓名:<input type="text" name="name">
<br/>
请选择要上传的文件:<input type="file" name="file" /> <br>
<input type="submit" value="上传" />
</form>
表单格式如下:
上传的数据为:
上述数据是一个多部分表单数据格式的字符串,它由以下几个部分组成:
------WebKitFormBoundarylSNPeJJRkGsWkuKA
,这是一个分隔符,用于区分不同的表单数据部分,它是由浏览器自动生成的,每个浏览器可能有不同的分隔符。
Content-Disposition: form-data; name="name"
,这是一个表单数据部分的头部,用于描述这个部分的信息,它有以下几个部分:
Content-Disposition
,这是一个 HTTP 头部字段,用于指定这个部分的处理方式,它的值是 form-data
,表示这是一个表单数据部分。name
,这是一个参数,用于指定这个部分的名称,它的值是 name
,表示这是用户输入的姓名。张三
,这是一个表单数据部分的内容,用于存储这个部分的数据,它的值是 张三
,表示用户输入的姓名是张三。
Content-Disposition: form-data; name="file"; filename="EDG.jpg"
,这是另一个表单数据部分的头部,用于描述这个部分的信息,它有以下几个部分:
Content-Disposition
,这是一个 HTTP 头部字段,用于指定这个部分的处理方式,它的值是 form-data
,表示这是一个表单数据部分。name
,这是一个参数,用于指定这个部分的名称,它的值是 file
,表示这是用户选择的文件。filename
,这是另一个参数,用于指定这个部分的文件名,它的值是 EDG.jpg
,表示用户选择的文件名是 EDG.jpg。Content-Type: image/jpeg
,这是另一个 HTTP 头部字段,用于指定这个部分的内容类型,它的值是 image/jpeg
,表示这是一个 JPEG 格式的图像文件。
------WebKitFormBoundarylSNPeJJRkGsWkuKA--
,这是一个结束符,用于标记多部分表单数据的结束,它是由分隔符加上两个连字符组成的。
使用FromData
的实例来转化为该类型,如果是文件,直接将对应的二进制作为键即可。不需要在请求头中设置Content-Type
,浏览器会自动添加。
// 前端使用fetch发送multipart/form-data类型的数据
const formData = new FormData();
formData.append('name', 'Tom');
formData.append('age', 18);
formData.append('file', input.files[0]); // input是一个文件输入框
fetch('https://example.com/api', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => console.log(data));
对于multipart/form-data类型,可以使用multer
中间件,它会将请求体中的文件数据解析为一个对象,存放在req.file或req.files属性中,而其他的文本数据则存放在req.body属性中。例如:
// 后端使用express接收multipart/form-data类型的数据
const express = require('express');
const multer = require('multer');
const app = express();
// 使用multer模块来解析请求体
const upload = multer({dest: 'uploads/'});
app.post('/api', upload.any(), (req, res) => {
// req.body是一个对象,其中每个键值对对应一个表单字段和值
console.log(req.body); // {name: 'Tom', age: '18'}
// req.files是一个数组,其中每个元素是一个文件对象
console.log(req.files); // [{fieldname: 'file', originalname: 'test.jpg', ...}]
});
multer会自动存储到后端目录中的uploads/
中,并且以二进制的文件格式存储。
打印的req.file
内容如下:
{
fieldname: 'file',
originalname: '9dd38fb5ly1h6728s4qegj22yo1o0af3.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: 'uploads/',
filename: '616f17e7a4206b72df293fdd918fd064',
path: 'uploads\\616f17e7a4206b72df293fdd918fd064',
size: 1420173
}
我们可以看到文件是二进制格式的,至于如何处理为源文件类型,可以参考下面的资料:
除此之外,还有一些关于multer的资料如下:
Express中,res.send()、res.json()、res.end()都是用来向客户端发送HTTP响应的方法,但是它们有一些区别。
如果您想发送字符串类型的数据,您可以使用res.send()、res.end()或res.render()方法。
// 后端使用res.send()发送一个字符串 app.get('/send', (req, res) => { let str = 'Hello world'; res.send(str); // 自动设置响应头的Content-Type为text/html }); // 前端使用fetch接收数据,并打印在控制台 fetch('/send') .then(response => response.text()) // 解析数据为字符串 .then(data => console.log(data)); // 打印Hello world // 后端使用res.end()发送一个字符串 app.get('/end', (req, res) => { let str = Buffer.from('Hello world').toString('hex'); // 将字符串转换为十六进制表示 res.end(str); // 不设置响应头的Content-Type }); // 前端使用fetch接收数据,并打印在控制台 fetch('/end') .then(response => response.text()) // 解析数据为字符串 .then(data => console.log(data)); // 打印48656c6c6f20776f726c64 // 后端使用res.render()发送一个字符串 app.get('/render', (req, res) => { let name = req.query.name || 'Bing'; // 获取请求参数中的name,如果没有就默认为Bing res.render('index', {name: name}); // 使用模板引擎渲染index视图,并传入name变量 }); // 前端使用fetch接收数据,并在网页中显示 fetch('/render?name=Bob') // 请求参数中传入name=Bob .then(response => response.html()) // 解析数据为HTML文档 .then(data => document.body.innerHTML = data); // 将网页的内容替换为渲染后的HTML
如果您想发送对象或数组类型的数据,您可以使用res.send()
或res.json()
方法。
// 后端使用res.send()发送一个对象 app.get('/send', (req, res) => { let obj = {name: 'Bing', age: 10}; res.send(obj); // 自动设置响应头的Content-Type为application/json }); // 前端使用fetch接收数据,并打印在控制台 fetch('/send') .then(response => response.json()) // 解析数据为JSON对象 .then(data => console.log(data)); // 打印{name: 'Bing', age: 10} // 后端使用res.json()发送一个数组 app.get('/json', (req, res) => { let arr = [1, 2, 3]; res.json(arr); // 自动设置响应头的Content-Type为application/json }); // 前端使用fetch接收数据,并打印在控制台 fetch('/json') .then(response => response.json()) // 解析数据为JSON对象 .then(data => console.log(data)); // 打印[1, 2, 3]
如果您想发送布尔值类型的数据,您可以使用res.send()或res.json()方法。
// 后端使用res.send()发送一个布尔值 app.get('/send', (req, res) => { let bool = true; res.send(bool); // 自动设置响应头的Content-Type为text/html }); // 前端使用fetch接收数据,并打印在控制台 fetch('/send') .then(response => response.text()) // 解析数据为字符串 .then(data => console.log(data)); // 打印true // 后端使用res.json()发送一个布尔值 app.get('/json', (req, res) => { let bool = false; res.json(bool); // 自动设置响应头的Content-Type为application/json }); // 前端使用fetch接收数据,并打印在控制台 fetch('/json') .then(response => response.json()) // 解析数据为JSON对象 .then(data => console.log(data)); // 打印false
如果您想发送Buffer类型的数据,您可以使用res.send()或res.end()方法。
// 后端使用res.send()发送一个Buffer app.get('/send', (req, res) => { let buf = Buffer.from('Hello world'); // 创建一个Buffer对象 res.send(buf); // 自动设置响应头的Content-Type为application/octet-stream }); // 前端使用fetch接收数据,并打印在控制台 fetch('/send') .then(response => response.arrayBuffer()) // 解析数据为ArrayBuffer对象 .then(data => console.log(data)); // 打印ArrayBuffer(11) [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100] // 后端使用res.end()发送一个Buffer app.get('/end', (req, res) => { let buf = Buffer.from('Hello world'); // 创建一个Buffer对象 res.end(buf); // 不设置响应头的Content-Type }); // 前端使用fetch接收数据,并打印在控制台 fetch('/end') .then(response => response.arrayBuffer()) // 解析数据为ArrayBuffer对象 .then(data => console.log(data)); // 打印ArrayBuffer(11) [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
当后端响应数据之后,我们使用fetch来获取数据,fetch提供了很多的方法来处理不同类型的数据,就像express
中使用中间件来将request
中的数据进行转化。
假设后端使用Express的res.send()方法发送了一个对象,如下:
// 后端代码
app.get('/send', (req, res) => {
let obj = {name: 'Bing', age: 10};
res.send(obj); // 自动设置响应头的Content-Type为application/json
});
那么前端使用fetch方法接收数据时,会得到一个Response对象,如下:
// 前端代码
fetch('/send')
.then(response => {
console.log(response); // 打印Response对象
return response.json(); // 解析数据为JSON对象
})
.then(data => {
console.log(data); // 打印{name: 'Bing', age: 10}
});
Response对象的结构大致如下:
// Response对象的结构
{
body: ReadableStream, // 响应的主体数据的流
bodyUsed: false, // 响应的主体数据是否已经被使用
headers: Headers, // 响应的头部信息的对象
ok: true, // 响应的状态码是否在200-299之间
redirected: false, // 响应是否经过重定向
status: 200, // 响应的状态码
statusText: "OK", // 响应的状态文本
type: "basic", // 响应的类型,可能是basic, cors, error, opaque, opaqueredirect等
url: "http://localhost:3000/send", // 响应的URL
data: {name: 'Bing', age: 10} // 响应的主体数据,需要使用相应的方法来解析
}
前端获取到数据的方法有以下几种,以下方法的返回值都是Promise对象,并且Promise的值为将response对象的data进行转换后的结果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。