Fetch API与POST请求那些事
- 2020 年 3 月 7 日
- 筆記
简述
相信不少前端开发童鞋与后端联调接口时,都会碰到前端明明已经传了参数,后端童鞋却说没有收到,尤其是post
请求,遇到的非常多。本文以node.js
作为服务端语言,借用express
框架,简要分析客户端发送post
请求的四种方式以及服务端如何接收。本文客户端请求没有借助第三方ajax
库,采用的是Fetch API
,虽然浏览器兼容性有点问题,但是用法简洁灵活,以后可能会是一个趋势。在说post
请求之前,先简要概述下Fetch API
。
Fetch API
Fetch API
提供了一个获取资源的接口(包括跨域请求),提供了更强大和灵活的功能集。未来可能是XMLHttpRequest
的一种替代方案。去年GitHub
代码去jQuery
重构时,就使用Fetch API
替代jQuery
的ajax
,毕竟目前JavaScript
很多原生语法都进行了大量精简,比如DOM
操作API
、http
请求fetch
、es6+
等。今天的axios
可能就是明日的jQuery
!
简单的实例
Fetch API
主要暴露了三个接口一个方法。
- 三个接口
Request
(资源请求)Response
(请求的响应)Headers
(Request/Response
头部信息)
- 一个方法
fetch()
(获取资源调用的方法)
// 实例化一个Request实例 // 第一个参数一般指资源路径 // 第二个参数可以理解为请求的配置项,包含头部信息和http请求一些关键配置(请求类型、参数...) let requestInstance = new Request('/hello', { method: 'post', headers: { 'Content-Type': 'application/json;charset=utf-8' }, body: '{"hello": "world"}' }) // fetch方法参数同Request实例 // 第一个参数为url或者Request实例 // 第二个参数为请求配置项 fetch(requestInstance).then(response => { // 返回的是一个Response的实例 // 调用Response实例的序列化方法,序列化成json,返回值是一个promise // 序列化方法有 json,text,formData,blob,arrayBuffer,redirct let result = response.json() result.then(res => { consolee.log(res) }) })
有意思的特性
Fetch API
添加了一个实验性的功能,支持客户端手动取消http
请求了,这个比较有意思,因为之前的ajax
貌似都不支持手动取消。借助AbortSignal
接口,可以通过AbortController
实例化一个控制器,将实例的siginal
当做请求的配置项,传递到服务端,客户端可以通过AbortController
实例的abort
方法,来终止当前的http
请求,示例代码如下:
<template> <button id='btn'>中止请求</button> </template> <script> // 实例化controller var controller = new AbortController() // 获取实例的signal接口 var signal = controller.signal let btn = document.getElementById('btn') // 点击按钮,中止请求 btn.addEventListener('click', e => { controller.abort() }) // json方式提交数据 const url = 'http://192.168.43.216:3000' // 将signal接口放到请求配置项中 let testRequest = new Request(url + '/test', { method: 'post', headers: { 'Content-Type': 'application/json;charset=utf-8;' }, body: '{"foo":"bar"}', signal }) fetch(testRequest).then(response => { let result = response.text() result.then(res => { console.log(res) }) }) </script>
post请求四种传参方式
本文所说的前端传递数据格式相对于主流的ajax
函数库有一定区别,一般的ajax
函数库为了方便用户使用,都会对数据进行二次封装。本文主要说原始的数据格式交互,具体ajax
库的使用,还是以官方文档为准。
请求头(Request Headers
)的实体Content-Type
用于指示资源的MIME
类型,即客户端传递消息的格式;响应头中Content-Type
用于指示服务端返回消息的格式。所以在http
请求中,我们可以从报文中的Content-Type
属性来判断客户端-服务端消息传递的格式。
JSON提交
JSON
是常用的一种前后端数据接收格式。前端传递的是键值对数据,即对象(Object
)。采用JSON
传递参数,请求头Content-Type
为application/json;charset=utf-8
,其中charset
为采用的字符集。
注意点:
- 既然为JSON提交,就要对参数进行序列化,即
JSON.stringify(params)
,否则传递到服务端的参数可能是[Object object]
- 服务端(
node.js
)是以流的方式进行接收,接收完是一个JSON
字符串,调用JSON.parse(params)
可以对参数进行序列化
示例代码
客户端:
const url = 'http://192.168.43.216:3000' let testRequest = new Request(url + '/test', { method: 'post', headers: { 'Content-Type': 'application/json;charset=utf-8;' }, body: JSON.stringify({a: 1}) }) fetch(testRequest).then(response => { let result = response.text() result.then(res => { console.log(res) }) })
服务端:
router.post('/test', (req, res, next) => { let data = '' req.on('data', chunk => { data += chunk }) req.on('end', () => { // 将JSON字符串解析成对象 data = JSON.parse(data) res.send(data) }) })
请求信息:
请求头提交
在实际开发中,遇到过不少后端开发,喜欢吧请求参数放在请求头,类似于get
请求,即请求的参数是拼接在请求地址后面。个人觉得这种传参方式并不好,一般浏览器对URL
长度是有限制的,以Chrome
为例,URL
最大长度正在7700
个字符左右,对于post
请求来说,最好参数还是放在body
中。
注意点
- 客户端请求参数拼接在
url
后,在?
后,键值对写法a=1
,多个键值对之间通过连接符&
连接 - 服务端能够在
request
对象中,通过request.query
直接进行接收 - 由于参数是拼接在
url
后面,所以请求头Content-Type
无需设置
示例代码
客户端:
let queryStringRequest = new Request(`${url}/querystring?a=1&b=2`, { method: 'post' }) fetch(queryStringRequest).then(response => { let result = response.json() result.then(res => { console.log(res) }) })
服务端:
router.post('/querystring', (req, res, next) => { res.send(req.query) })
请求信息:
普通表单提交
表单提交的方式有两种,一种是普通的表单提交,另外一种是通过FormData
进行提交(主要应用在文件上传)。单纯的表单提交,与上述两种参数格式上还是存在一定的差别的,主要体现在以下几个方面。
Content-Type
表单提交Request Headers
的Content-Type
为application/x-www-form-urlencoded;charset=utf-8
。- 参数
表单提交参数是放在body
中,感觉是JSON
和请求头提交的合体。参数位置与JSON
提交相同,参数格式与请求头提交一致
示例代码
客户端:
let formRequest = new Request(url + '/form', { method: 'post', headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;' }, body: 'a=1&b=2' }) fetch(formRequest).then(response => { let result = response.json() result.then(res => { console.log(res) }) })
服务端:
const fs = require('fs') router.post('/form', (req, res, next) => { let data = '' req.on('data', chunk => { data += chunk }) req.on('end', () => { data = decodeURI(data) // 将a=1&b=2解析成{a: 1, b: 2} let dataObj = querystring.parse(data) res.send(dataObj) }) })
请求信息:
FormData提交 (文件上传)
通常我们在进行文件上传时,都会采用表单提交。参数放在body
中,只不过格式与普通的有差别,具体如下:
- 参数需要放在
FormData
的实例中,通过append
进行参数的添加 - 请求头
Content-Type
为multipart/formdata
示例代码
客户端:
<template> <input type="file" id="uploadFile"> </template> <script> let $input = document.getElementById('uploadFile') // 监听文件上传 $input.addEventListener('change', e => { let file = e.target.files[0] handleUploadFile(file) }) function handleUploadFile (file) { let bean = new FormData() bean.append('file', file) bean.append('hello', 'world') let uploadFileRequest = new Request(`${url}/upload`, { method: 'post', headers: { 'Content-Type': 'multipart/formdata' }, body: bean }) fetch(uploadFileRequest).then(response => { let result = response.text() result.then(res => { console.log(res) }) }) } </script>
服务端:
router.post('/upload', (req, res, next) => { let data = [] let size = 0 req.on('data', chunk => { data.push(chunk) size += chunk.length }) let rems = [] req.on('end', () => { let buffer = Buffer.concat(data, size) for (let i = 0; i < buffer.length; i++) { var v = buffer[i]; var v2 = buffer[i+1]; if(v==13 && v2==10){ rems.push(i); } } // 图片信息 var picmsg_1 = buffer.slice(rems[0]+2,rems[1]).toString(); var filename = picmsg_1.match(/filename=".*"/g)[0].split('"')[1]; // 图片数据 var nbuf = buffer.slice(rems[3]+2,rems[rems.length-2]); var path = './static/'+filename; fs.writeFileSync(path , nbuf); res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8'}); res.end('<div id="path">'+path+'</div>'); }) })
请求信息:
小结
post
请求向服务端提交参数,一般情况下都是放在body
中,但是从上文列举的几种传参方式,仍然可以放在请求头中传递,服务端对于在请求头中传递的参数的处理和get
请求保持一致。此外,从node.js
接收的参数来看,除了放在请求头中能够直接获取外,其余三种请求方式都是以字节流的方式传递到服务端的。熟悉post
请求的几种传参方式,有助于我们和后端同学进行接口联调。