详解axios在node.js中的post使用

 更新时间:2017年04月27日 09:57:20   作者:chux0519  
最近因为工作的原因在学习使用网络请求库,因为这个项目用的是Promise,所以就选择了axios,下面这篇文章主要给大家介绍了关于axios在node.js中的post使用的相关资料,文中介绍的非常详细,需要的朋友可以参考借鉴,下面来一起学习学习吧。

脚本之家 / 编程助手:解决程序员“几乎”所有问题!
脚本之家官方知识库 → 点击立即使用

前言:

最近因为做的东西需要用到网络请求库,之前接触过的只有request,很强大好用。但是这个项目中需要用到Promise,我又不想重新封装,于是选择了另一款库axios。

在node中,axios的get请求加上原生支持的Promise语法使用起来很方便,很丝滑,但是后面碰到了一个需求,就是要向另一个服务器post数据,并且这个数据是以form-data的形式post过去的,这时,问题就出现了。

问题:

当我想在node中使用axios以post的方式发送一张图片给某个server时,最先我是尝试这样做:

方案一

1
2
3
4
5
6
7
8
let data = fs.createReadStream(__dirname + '/test.jpg')
axios.post(url,{media:data,type:"image"})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
})

事实证明,这样做是完全没有用的,我尝试向另一个服务器poststream,返回的总是错误。然而,如果我使用request,下面这样的代码是完全没有问题的:

方案二

1
2
3
4
5
6
7
8
9
10
let data = fs.createReadStream(__dirname + '/test.jpg')
let form = {
type:"image",
media:data
}
 
request.post({url:url,formData:form},(err,res,body)=>{
if(err) console.log(err)
console.log(body)
})

探索:

于是,我陷入了思考,WTF!!

我打算简单的写一个服务器,用于打印HTTP请求,然后查看区别(别问我为什么不用抓包工具,任性!),代码呼之欲出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Koa from 'koa'
 
const app = new Koa()
 
app.use(ctx=>{
console.log("===============================================")
console.log(ctx.request)
console.log("===============================================")
ctx.body = {foo:"bar"}
})
 
app.listen(3000,()=>{
console.log("listening on 3000 port")
})

此时,将url设置为:http://127.0.0.1:3000/,再分别执行方案一和方案二 这时打印出了这样的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
listening on 3000 port
===============================================
{ method: 'POST',
url: '/',
header:
{ accept: 'application/json, text/plain, */*',
'content-type': 'application/json;charset=utf-8',
'user-agent': 'axios/0.14.0',
'content-length': '587',
host: '127.0.0.1:3000',
connection: 'close' } }
===============================================
===============================================
{ method: 'POST',
url: '/',
header:
{ host: '127.0.0.1:3000',
'content-type': 'multipart/form-data; boundary=--------------------------949095406788084443059291',
'content-length': '186610',
connection: 'close' } }
===============================================

- 上面的是方案一,下面的是方案二

这时可以看出,方案一和二的差别最明显的是content-type,是的,这也是决定了方案一不可行的因素。 既然是content-type导致的,那么方案一PLUS就比较明了了,查阅axios的文档后,我决定手动设置content-type,于是乎:

1
2
3
4
5
6
7
8
9
10
11
let data = fs.createReadStream(__dirname + '/test.jpg')
let header = {
'content-type': 'multipart/form-data'
}
axios.post(url,{media:data,type:"image"},{headers:header})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
})

- 这时,请求是这样的:

1
2
3
4
5
6
7
8
9
10
11
===============================
{ method: 'POST',
url: '/',
header:
{ accept: 'application/json, text/plain, */*',
 'content-type': 'multipart/form-data',
 'user-agent': 'axios/0.14.0',
 'content-length': '587',
 host: '127.0.0.1:3000',
 connection: 'close' } }
================================

貌似差别不大,但我先试着往服务器post数据时,仍然返回错误。实际上这时候没有boundary,文件其实并没有被绑定上去,所以现在仍然没有解决问题。至于boundary,这里有个链接非常能说明问题。

到这里,我们就要耐下心来好好思考了,区别就在于,request中能够设置正确的请求头,那么它是怎么办到的呢,于是我开始翻看request的源码,发现了这一段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if (options.formData) {
var formData = options.formData
var requestForm = self.form()
var appendFormValue = function (key, value) {
if (value && value.hasOwnProperty('value') && value.hasOwnProperty('options')) {
requestForm.append(key, value.value, value.options)
} else {
requestForm.append(key, value)
}
}
for (var formKey in formData) {
if (formData.hasOwnProperty(formKey)) {
var formValue = formData[formKey]
if (formValue instanceof Array) {
for (var j = 0; j < formValue.length; j++) {
appendFormValue(formKey, formValue[j])
}
} else {
appendFormValue(formKey, formValue)
}
}
}
}

这一段是request在初始化参数中的formData,其中调用了它自身的form()方法,追踪这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Request.prototype.form = function (form) {
var self = this
if (form) {
if (!/^application\/x-www-form-urlencoded\b/.test(self.getHeader('content-type'))) {
self.setHeader('content-type', 'application/x-www-form-urlencoded')
}
self.body = (typeof form === 'string')
? self._qs.rfc3986(form.toString('utf8'))
: self._qs.stringify(form).toString('utf8')
return self
}
// create form-data object
self._form = new FormData()
self._form.on('error', function(err) {
err.message = 'form-data: ' + err.message
self.emit('error', err)
self.abort()
})
return self._form
}

发现了request调用了另一个库form-data,先通过self.form()创建出一个formData对象,再遍历options里的formData项,递归地将内容通过formData的append方法放进去,也就是说是formData实现了post文件,于是乎,我在axios中插入formData,形成了方案三:

方案三:

1
2
3
4
5
6
7
8
9
let data = fs.createReadStream(__dirname + '/test.jpg')
let form = new FormData()
form.append('type','image')
form.append('media',data,'test.jpg')
 
axios.post(url,form).then((response)=>{
console.log(response.data)
})
.catch(e=>{console.log(e)})

但是,事实告诉我,我还是悲剧了,请求打印出来是这样的:

1
2
3
4
5
6
7
8
9
10
11
===============================================
{ method: 'POST',
url: '/',
header:
{ accept: 'application/json, text/plain, */*',
'content-type': 'application/x-www-form-urlencoded',
'user-agent': 'axios/0.14.0',
host: '127.0.0.1:3000',
connection: 'close',
'transfer-encoding': 'chunked' } }
===============================================

再次content-type还是不对,于是我再去翻axios的文档和issue,发现,默认设置的content-type就是application/x-www-form-urlencoded,于是我判断,一定还是要手动设置headers的

于是,基于方案三,我又添加了和改动了这两行形成了方案四:

方案四

1
2
3
4
5
6
7
let header = {
'content-type': 'multipart/form-data'
}
 
axios.post(url,form,{headers:header}).then((response)=>{
console.log(response.data)
})

但结果还是不理想,直接设置content-type是不行的,因为要将待发送文件绑定,就一定会有boundary出现,另外在方案三和方案四的请求中,出现了transfer-encoding这个值,关于这个chunked,可以参考MDN这篇文章

一边google一边看文档的我,发现formData的文档中出现过form.getHeaders()的写法,于是方案五出现了:

方案五

1
2
3
4
5
6
7
8
9
let data = fs.createReadStream(__dirname + '/test.jpg')
let form = new FormData()
form.append('type','image')
form.append('media',data,'test.jpg')
 
axios.post(url,form,{headers:form.getHeaders()}).then((response)=>{
console.log(response.data)
})
.catch(e=>{console.log(e)})

但是结果表明,这样还是不行,现在的请求是这样:

1
2
3
4
5
6
7
8
9
10
11
===============================================
{ method: 'POST',
url: '/',
header:
 { accept: 'application/json, text/plain, */*',
'content-type': 'multipart/form-data; boundary=--------------------------171407872885673042671614',
'user-agent': 'axios/0.14.0',
host: '127.0.0.1:3000',
connection: 'close',
'transfer-encoding': 'chunked' } }
===============================================

但是我目前项目需求是,不使用chunked而采用content-length的方法来传输,这意味着,我要想办法搞到form的长度

在成功案例中,使用requests,于是我翻看了部分源码: 在request/request.js里出现了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function setContentLength () {
if (isTypedArray(self.body)) {
 self.body = new Buffer(self.body)
}
 
if (!self.hasHeader('content-length')) {
 var length
 if (typeof self.body === 'string') {
length = Buffer.byteLength(self.body)
 }
 else if (Array.isArray(self.body)) {
length = self.body.reduce(function (a, b) {return a + b.length}, 0)
 }
 else {
length = self.body.length
 }
 
 if (length) {
self.setHeader('content-length', length)
 } else {
self.emit('error', new Error('Argument error, options.body.'))
 }
}
}

它采用Buffer来计算长度,然后添加到headers中去

然后看看在axios里是如何做的: axios/lib/adapters/http.js里出现了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (data && !utils.isStream(data)) {
 if (utils.isArrayBuffer(data)) {
data = new Buffer(new Uint8Array(data));
 } else if (utils.isString(data)) {
data = new Buffer(data, 'utf-8');
 } else {
return reject(createError(
 'Data after transformation must be a string, an ArrayBuffer, or a Stream',
 config
));
 }
 
 // Add Content-Length header if data exists
 headers['Content-Length'] = data.length;
}

下文并没有出现else,所以,当data是stream的时候,并没有自动设置content-length

所以,我需要在formData.getHeaders()后,再添加一个content-length的key

想要计算长度,自然想到去看看源码,于是在form-data/lib/form_data.js中出现了惊喜:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
FormData.prototype.getLength = function(cb) {
var knownLength = this._overheadLength + this._valueLength;
 
if (this._streams.length) {
knownLength += this._lastBoundary().length;
}
 
if (!this._valuesToMeasure.length) {
process.nextTick(cb.bind(this, null, knownLength));
return;
}
 
asynckit.parallel(this._valuesToMeasure, this._lengthRetriever, function(err, values) {
if (err) {
 cb(err);
 return;
}
 
values.forEach(function(length) {
 knownLength += length;
});
 
cb(null, knownLength);
});
};

formData已经封装好了得到长度的方法,只不过它是异步的,不过没关系,在实际项目中,可以将它手动Promise化。最终方案的代码也就自然出现了:

方案六:

1
2
3
4
5
6
7
8
9
10
11
12
let data = fs.createReadStream(__dirname + '/test.jpg')
let form = new FormData()
form.append('type','image')
form.append('media',data,'test.jpg')
form.getLength((err,length)=>{
if(err) console.log(err)
let headers = Object.assign({'Content-Length':length},form.getHeaders())
axios.post(url,form,{headers:headers}).then((response)=>{
console.log(response.data)
})
.catch(e=>{console.log(e)})
})

这时的请求打印后是这样的:

1
2
3
4
5
6
7
8
9
10
11
===============================================
{ method: 'POST',
url: '/',
header:
{ accept: 'application/json, text/plain, */*',
'content-type': 'multipart/form-data; boundary=--------------------------424584867554529984619649',
'content-length': '186610',
'user-agent': 'axios/0.14.0',
host: '127.0.0.1:3000',
connection: 'close' } }
===============================================

事实证明它是可以工作的。

更进一步,我们把异步代码Promise一下,得到最终方案:

最终方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let data = fs.createReadStream(__dirname + '/test.jpg')
let form = new FormData()
form.append('type','image')
form.append('media',data,'test.jpg')
 
let getHeaders = (form=>{
return new Promise((resolve,reject)=>{
form.getLength((err,length)=>{
if(err) reject(err)
let headers = Object.assign({'Content-Length':length},form.getHeaders())
resolve(headers)
})
})
})
 
getHeaders(form)
.then(headers=>{
return axios.post(url,form,{headers:headers})
})
.then((response)=>{
console.log(response.data)
})
.catch(e=>{console.log(e)})

总结

得到一个结论,多多看issue,多多看源码,多多了解基础知识(HTTP协议),对于问题的解决十分重要。最后这一套的实验代码放在github上了,需要研究研究的同学们可以看看:axios-request或者下载到本地学习

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

原文链接:http://cnodejs.org/topic/57e17beac4ae8ff239776de5

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!

相关文章

  • node.js中优雅的使用Socket.IO模块的方法

    node.js中优雅的使用Socket.IO模块的方法

    Socket.IO是一个WebSocket库,包括了客户端的js和服务器端的node.js,它的目标是构建可以在不同浏览器和移动设备上使用的实时应用,这篇文章主要介绍了node.js中优雅的使用Socket.IO模块,需要的朋友可以参考下
    2022-12-12
  • Node.js实现http请求服务与Mysql数据库操作方法详解

    Node.js实现http请求服务与Mysql数据库操作方法详解

    这篇文章主要介绍了Node.js实现http请求服务与Mysql数据库操作方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-10-10
  • 利用nodeJs anywhere搭建本地服务器环境的方法

    利用nodeJs anywhere搭建本地服务器环境的方法

    今天小编就为大家分享一篇利用nodeJs anywhere搭建本地服务器环境的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-05-05
  • nodejs入门教程六:express模块用法示例

    nodejs入门教程六:express模块用法示例

    这篇文章主要介绍了nodejs入门教程之express模块用法,结合实例形式分析了express模块的功能、创建、路由相关使用技巧,需要的朋友可以参考下
    2017-04-04
  • Nodejs从有门道无门菜鸟起飞必看教程

    Nodejs从有门道无门菜鸟起飞必看教程

    下面小编就为大家带来一篇Nodejs从有门道无门菜鸟起飞必看教程。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-07-07
  • Node.js安装详细步骤教程(Windows版)详解

    Node.js安装详细步骤教程(Windows版)详解

    这篇文章主要介绍了Node.js安装详细步骤教程(Windows版),本文图文并茂给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-09-09
  • Node.js中Swagger的使用指南详解

    Node.js中Swagger的使用指南详解

    Swagger(目前用OpenAPI Specification代替)是一个用于设计、构建、记录和使用REST API的强大工具,本文将探讨使用Swagger的一些关键技巧,需要的可以参考一下
    2024-01-01
  • Yapi安装部署详细图文教程

    Yapi安装部署详细图文教程

    YApi 是一个可本地部署的、打通前后端及QA的、可视化的接口管理平台,下面这篇文章主要给大家介绍了关于Yapi安装部署的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-09-09
  • 小结Node.js中非阻塞IO和事件循环

    小结Node.js中非阻塞IO和事件循环

    本文针对在Node.js关键的两个概念:非阻塞IO和事件循环进行了适当的总结,需要的朋友可以参考下
    2014-09-09
  • 利用python分析access日志的方法

    利用python分析access日志的方法

    最近在自学python,光看是不行的,还是要多实操的。这就是一个写给自己的小小工具。主要功能是,便利日志文件夹下的文件,根据设定的关键字查找是否有web攻击行为。有需要的朋友们下面来一起看看吧。
    2016-10-10

最新评论