最近有个需求需要用到OSS对象存储的Post Object API,它跟平常用的Put Object操作有点不一样,Put操作用阿里云提供的SDK就能上传了。但Post要把请求参数作为消息体中的表单域传递。这就需要我们自己组装表单请求元素了。
遇到这个需求刚开始还不慌的,毕竟有文档!当我看了一轮后,发现只有JAVA表单上传的示例,其他全部语言都没有的。这不是为难我这个小小前端仔吗?
在阿里云官网提交了工单找售后仍然没有解决。没有办法,只能食自己啦!
先从百度找到了一个nodeJS模拟表单上传的代码,细想一波,这跟我要弄的不就是差个OSS?然后对代码进行改造。
1. 基础版本:nodejs模拟HTML表单上传文件
参考这个博文就行了:https://blog.csdn.net/weixin_30662849/article/details/97466324
具体代码:
const http = require('http')
const path = require('path')
const fs = require('fs')
function postFile(fileKeyValue, req) {
// boundary为边界字符串,无需指定,随便生成就行
var boundaryKey = Math.random().toString(16);
var enddata = 'rn----' + boundaryKey + '--';
var files = new Array();
for (var i = 0; i < fileKeyValue.length; i++) {
// 组装表单的请求元素: Content-Type以及其他需要的元素
var content = "rn----" + boundaryKey + "rn" +
"Content-Type: application/octet-streamrn" +
"Content-Disposition: form-data; name="" + fileKeyValue[i].urlKey +
""; filename="" + path.basename(fileKeyValue[i].urlValue) +
""rn" + "Content-Transfer-Encoding: binaryrnrn"
var contentBinary = Buffer.from(content, 'utf-8');//当编码为ascii时,中文会乱码。
files.push({contentBinary: contentBinary, filePath: fileKeyValue[i].urlValue});
}
var contentLength = 0;
for (var i = 0; i < files.length; i++) {
// 读取文件状态
var stat = fs.statSync(files[i].filePath);
contentLength += files[i].contentBinary.length;
contentLength += stat.size;
}
// 添加请求头
req.setHeader('Content-Length', contentLength + Buffer.byteLength(enddata));
req.setHeader('Content-Type', 'multipart/form-data; boundary=--' + boundaryKey);
// 将参数发出
var fileindex = 0;
var doOneFile = function(){
req.write(files[fileindex].contentBinary);
var fileStream = fs.createReadStream(files[fileindex].filePath, {bufferSize : 4 * 1024});
fileStream.pipe(req, {end: false});
fileStream.on('end', function() {
fileindex++;
if(fileindex == files.length){
req.end(enddata);
console.log('文件读取结束')
} else {
doOneFile();
}
});
};
if(fileindex == files.length){
req.end(enddata);
} else {
doOneFile();
}
}
//测试用例
var files = [ {urlKey: "file1", urlValue: "D:\test.zip"} ]
// 自己创建服务器的路径:接收上传文件
var urlStr = 'http://localhost:3001/upload/test'
var req = http.request(urlStr, {
method: 'POST'
}, function(res){
console.log("RES:" + res);
console.log('STATUS: ' + res.statusCode);
console.log('HEADERS: ' + JSON.stringify(res.headers));
res.on("data", function(chunk){
console.log("BODY:" + chunk);
})
})
req.on('error', function(e){
console.log('problem with request:' + e.message);
console.log(e);
})
// console.log('req: ', req)
postFile(files, req)
console.log("done")
2. 改造:OSS PostObject Node(express)版本
对着官网查参数 https://help.aliyun.com/document_detail/31988.html
-
请求语法-请求体:只上传了必填参数
注意:file必须为最后一个表单域,我另外用一个变量 content 列出来了
const boundary = 9431149156168
const enddata = `rn--${boundary}--`
const formContent = `--${boundary}rnContent-Disposition: form-data; name="key"rnrn${key}`
+ `rn--${boundary}rnContent-Disposition: form-data; name="Content-Disposition"rnrnattachment;filename=${fileName}`
+ `rn--${boundary}rnContent-Disposition: form-data; name="OSSAccessKeyId"rnrn${OSSAccessKeyId}`
+ `rn--${boundary}rnContent-Disposition: form-data; name="policy"rnrn${policy}`
+ `rn--${boundary}rnContent-Disposition: form-data; name="Signature"rnrn${signature}`
let content = `rn--${boundary}rnContent-Disposition: form-data; name="file"; filename="${fileName}"rnContent-Type: application/octet-streamrnrn`
对比官方文档语法,还差一个file_content,那就用fs模块读取文件,顺便将文件的长度读出来放在Headers上。
fs.readFile(filePath, 'binary', function(err, data) {
if(err) return err
content += data
formBinary = Buffer.from(formContent, 'utf-8') //当编码为ascii时,中文会乱码。
contentBinary = Buffer.from(content, 'utf-8')
const absolutePath = path.resolve(__dirname, `../${filePath}`)
let contentLength = 0
const stat = fs.statSync(absolutePath)
contentLength += formBinary.length + contentBinary.length
contentLength += stat.size
}
-
请求语法-请求头 需要的是:Method、Host、User-Agent、Content-Length、Content-Type,那就在http请求上设置,这里http.request返回的req是一个Http.client可读写的示例(node官网有说)
req.setHeader('Host', host)
req.setHeader('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36')
req.setHeader('Content-Length', contentLength + Buffer.byteLength(enddata))
req.setHeader('Content-Type', 'multipart/form-data; boundary='+ boundaryKey)
-
发送请求 现在Post Object的请求元素写好了,开始把请求元素写入http/https请求中,并把文件发送出去。
req.write(formBinary)
req.write(contentBinary)
const fileStream = fs.createReadStream(filePath, {bufferSize : 4 * 1024})
fileStream.pipe(req, {end: false})
fileStream.on('end', function() {
req.end(enddata)
})
OK参数组装完毕。
3. 完整代码示例:
这里返回的逻辑写在req的回调函数里面原因:
-
req请求后只能通过回调函数的形式处理 -
定义req我用的是const,所以不能放在读取文件fs.readFile之后了
const fs = require('fs')
const pathMod = require('path')
const https = require('https')
const path = '你文件的路径'
//用来获取IOT平台返回的参数的(我这是通过IOT平台API返回的)
const result = await GenerateOTAUploadURL()
const { Policy, Signature, OSSAccessKeyId, Host, Key, FirmwareUrl} = result.Data
// http请求路径:'https://iotx-ota.oss-cn-shanghai.aliyuncs.com'
const urlStr = Host
// 创建http/https请求实例
const req = https.request(urlStr, { method: 'POST', 'Content-Type': 'multipart/form-data;boundary=9431149156168' }, async function(postRes) {
// 上传升级包后的回调
console.log('STATUS: ' + postRes.statusCode)
// console.log('HEADERS: ' + JSON.stringify(res.headers))
// 匹配状态码已2开头,我发送请求地址为https://iotx-ota.oss-cn-shanghai.aliyuncs.com,它没有返回内容,只有状态码204
const reg = /^2/
if (reg.test(postRes.statusCode)) {
//文件上传成功后把本地服务器里的删了(浪费内存)
fs.unlink(pathMod.resolve(__dirname, `../${path}`), (err) => next(err))
res.status(200).json({msg: '上传成功!', success: 1 })
}
postRes.on("data", function(chunk) {
console.log("BODY:" + chunk)
})
})
//监听上传时的错误
req.on('error', function(e) {
console.log('problem with request:' + e.message)
console.log(e)
})
// 上传文件 post Object OSS
const boundary = 9431149156168
const enddata = 'rn--' + boundary + '--'
const formContent = `--${boundary}rnContent-Disposition: form-data; name="key"rnrn${Key}`
+ `rn--${boundary}rnContent-Disposition: form-data; name="Content-Disposition"rnrnattachment;filename=${originalname}`
+ `rn--${boundary}rnContent-Disposition: form-data; name="OSSAccessKeyId"rnrn${OSSAccessKeyId}`
+ `rn--${boundary}rnContent-Disposition: form-data; name="policy"rnrn${Policy}`
+ `rn--${boundary}rnContent-Disposition: form-data; name="Signature"rnrn${Signature}`
let content = `rn--${boundary}rnContent-Disposition: form-data; name="file"; filename="${originalname}"rnContent-Type: application/octet-streamrnrn`
let formBinary, contentBinary
fs.readFile(path, 'binary', function(err, data) {
if(err) return err
content += data
formBinary = Buffer.from(formContent, 'utf-8') //当编码为ascii时,中文会乱码。
contentBinary = Buffer.from(content, 'utf-8')
const absolutePath = pathMod.resolve(__dirname, `../${path}`)
let contentLength = 0
const stat = fs.statSync(absolutePath)
contentLength += formBinary.length + contentBinary.length
contentLength += stat.size
//oss
req.setHeader('Host', 'iotx-ota.oss-cn-shanghai.aliyuncs.com')
req.setHeader('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36')
req.setHeader('Content-Length', contentLength + Buffer.byteLength(enddata))
req.setHeader('Content-Type', 'multipart/form-data; boundary='+ boundary)
// 将参数发出
const doOneFile = function(){
req.write(formBinary)
req.write(contentBinary)
const fileStream = fs.createReadStream(path, {bufferSize : 4 * 1024})
fileStream.pipe(req, {end: false})
fileStream.on('end', function() {
req.end(enddata)
})
}
doOneFile()
})
被这个问题卡了差不多一周,我太菜了!先是想仿照Java示例实现的,后来发现不会,最后老老实实按照文档的语法一步一步来。
继续记录一个前端菜狗的成长。
原文始发于微信公众号(Hephaestuses):OSS对象存储PostObject Node版本
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/45002.html