Fork me on GitHub

fetch+node实现上传图片

图片上传

最近给设计妹妹搞了模板上传功能,里面涉及到了图片上传功能。

input

在前端涉及到图片上传,都会用到input标签,监听onchange事件,可以拿到file文件

1
<input type="file" accept=".png" class="input_file" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let file = document.getElementsByClassName('input_file')[0];

file.onchange = function() {
console.log(this.files);

if(!this.files.length) {
return;
}

let file = this.files[0];

let formData = new FormData();

formData.append('picture', file);

}

利用formData可以把二进制数据传输给后端。

fetch

由于是内部项目直接使用fetch

1
2
3
4
5
6
7
8
9
10
var ajaxImg = (url, params = {}) => {
return fetch(url, {
credentials: 'include',
method: 'POST',
headers: {
'Content-type': 'multipart/form-data;'
},
body: params
}).then((res) => res.json())
}

封装一个请求,单独传输图片数据。 在之前onchange里面调用ajaxImg将formData数据发送给后端。

fetch数据异常

后端一直没有拿到图片的数据,换成jquery的ajax就可以。

对比network,发现fetch的request content-type 里面内容少了 boundary=XXXXXXX

后来发现这个是浏览器的BUG,解决方案就是在请求的时候不需要加content-type。

这样前端的基本任务就完成了。

node

后端使用express来构建服务,解析form-data数据,要用到multer

代码简单如下

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
26
27
28
29
var fs = require("fs");
var express = require('express');
var bodyParser = require('body-parser');
var multer = require('multer');
var app = express();

var port = 8012;

var storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, './public/img');
},
filename: function(req, file, cb) {
cb(null, `${file.originalname}`)
}
})
var upload = multer({ storage: storage });

app.use(bodyParser.json());
app.use(express.static('public'));

app.post('/upload', upload.single('picture'), function(req, res, next) {
console.log(req.file);
res.json({
success: true
})
})

app.listen(port);

multer

借助multer能很快的处理图片问题,但是它是怎么处理的呢?

multer-> busboy -> dicer

dicer is A very fast streaming multipart parser for node.js.

所以dicer是其核心,我们尝试直接使用dicer来处理图片。

dicer

稍微改改官方的例子

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
var inspect = require('util').inspect,
http = require('http');

const fs = require('fs');

var Dicer = require('dicer');

// quick and dirty way to parse multipart boundary
var RE_BOUNDARY = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i,
HTML = new Buffer('<html><head></head><body>\
<form method="POST" enctype="multipart/form-data">\
<input type="text" name="textfield"><br />\
<input type="file" name="filefield" accept=".png"><br />\
<input type="submit">\
</form>\
</body></html>'),
PORT = 8012;

http.createServer(function (req, res) {
var m;
if (req.method === 'POST' &&
req.headers['content-type'] &&
(m = RE_BOUNDARY.exec(req.headers['content-type']))) {
var d = new Dicer({
boundary: m[1] || m[2]
});

d.on('part', function (p) {
console.log('New part!');
p.on('header', function (header) {
for (var h in header) {
console.log('Part header: k: ' + inspect(h) +
', v: ' + inspect(header[h]));
}
});
p.on('data', function (data) {
fs.writeFileSync('./test1.png', data);
console.log('Part data: ' + data);
});
p.on('end', function () {
console.log('End of part\n');
});
});
d.on('finish', function () {
console.log('End of parts');
res.writeHead(200);
res.end('Form submission successful!');
});
req.pipe(d);
} else if (req.method === 'GET' && req.url === '/') {
res.writeHead(200);
res.end(HTML);
} else {
res.writeHead(404);
res.end();
}
}).listen(PORT, function () {
console.log('Listening for requests on port ' + PORT);
});

fs.writeFileSync('./test1.png', data);

直接在请求数据回调里面保存为图片,发现是能够成功的。

在这个例子里完全没有express了,那我们尝试下自己来处理下图片数据。

原生处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const fs = require('fs');
const http = require('http');
const port = 8013;

http.createServer((req, res)=> {
let url = req.url;

if(url.indexOf('/upload')>-1) {
let strs = '';
req.on('data', (str)=> {
strs += str;
});
req.on('end', ()=> {
res.write(JSON.stringify({
"success": true
}));
fs.writeFileSync('./text', strs);
res.end();
})
}

}).listen(port);

核心代码如上,这样可以拿到formdata里面所有的数据,可以查看其数据。

可以发现其数据是通过请求头里面的boundary值进行分割的。

所以先简单取出boundary值

1
2
let type = req.headers['content-type'];
let boundary = type.split('boundary=')[1];

然后进行数据切割

1
2
strs = strs.split('--'+boundary + '--')[0];
let forms = strs.split('--'+boundary + '\r\n').filter(val=> val);

每块内容区域根据\r\n\r\n\r\n进行切割,可以将Content-Disposition、Content-Type, 内容区分开来。

完整代码可以尝试如下

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
const fs = require('fs');
const http = require('http');
const port = 8013;

http.createServer((req, res)=> {
let url = req.url;
let statics = 'html#png#js';
if(url === '/') {
url = '/index.html';
}
if(url.indexOf('.')>-1) {
let name = url.replace(/.*\./gim, '');
if(statics.indexOf(name) > -1) {
res.write(fs.readFileSync(`./public/${url}`));
res.end();
}
}

if(url.indexOf('/query')>-1) {
res.writeHead(200, {
'Content-Type': 'application/json;charset=utf-8;'
});
res.write(JSON.stringify({
"216*216": "./img/216*216.png"
}));
res.end();
}

if(url.indexOf('/upload')>-1) {
let strs = '';
let type = req.headers['content-type'];
let boundary = type.split('boundary=')[1];
req.on('data', (str)=> {
strs += str.toString('binary');
})
req.on('end', ()=> {

fs.writeFileSync('./text', buffer.toString());
res.writeHead(200, {
'Content-Type': 'application/json;charset=utf-8;'
});

strs = strs.split('--'+boundary + '--')[0];

let forms = strs.split('--'+boundary + '\r\n').filter(val=> val);

let formData = [];

forms.forEach(val=> {

let vals = val.split('\r\n\r\n');

let value = vals[1];

let vlass = vals[0].split('\r\n').filter(val=> val);

let name = vlass[0].match(/name="([\w]{0,})"/)[1];

let type;

if(vlass[1] && vlass[1].indexOf('Content-Type') > -1) {
type = vlass[1].match(/Content-Type: (.*)/)[1];
fs.writeFileSync('./test1.png', new Buffer(value , 'binary'));
}else {
type = 'text/plain';
value = value.split('\r\n')[0];
}

formData.push({
type,
name,
value
})

})

console.log(formData);

res.write(JSON.stringify({
"success": true
}));

res.end();
})
}

}).listen(port);

这里会发现这么使用

1
2
3
req.on('data', (str)=> {
strs += str.toString('binary');
})

这个原因在于request里面使用的是buffer数据结构,如果像字符串操作的话,不管咋样都无法保存图片的。

而dicer是直接对buffer数据进行处理的。

总结

通过这一波的折腾算是对整个图片上传流程有了了解。当然在正式项目里面还是建议大家去用已经封装好的模块,能够避免很多问题。

参考

  1. nodejs学习之文件上传

本文地址 http://xiaoqiang730730.github.io/2018/06/21/upload/

觉得有点意思,打个赏鼓励博主继续写哈!がんばって
前端-小强 WeChat Pay

微信打赏

前端-小强 Alipay

支付宝打赏