一.背景
在文件相关的数据加工等场景下,经常面临生成的物理文件应该如何处理的问题,比如:
生成的文件放到哪里,路径存在不存在?
临时文件何时清理,如何解决命名冲突,防止覆盖?
并发场景下的读写顺序如何保证?
……
对于读写物理文件带来的这些问题,最好的解决办法就是 不写文件 。然而,一些场景下想要不写文件可不那么容易,比如文件上传
二.问题
文件上传一般通过表单提交来实现,例如:
var FormData = require('form-data'); var fs = require('fs'); var form = new FormData(); form.append('my_file', fs.createReadStream('/foo/bar.jpg')); form.submit('example.org/upload', function(err, res) { console.log(res.statusCode); });
(摘自 Form-Data )
不想写物理文件的话,可以这样做:
const FormData = require('form-data'); const filename = 'my-file.txt'; const content = 'balalalalala...变身'; const formData = new FormData(); // 1.先将字符串转换成Buffer const fileContent = Buffer.from(content); // 2.补上文件meta信息 formData.append('file', fileContent, { filename, contentType: 'text/plain', knownLength: fileContent.byteLength });
也就是说,文件流除了能够提供数据外,还具有一些 meta 信息,如文件名、文件路径等 ,而这些信息是普通 Stream 所不具备的。那么,有没有办法凭空创建一个“真正的”文件流?
三.思路
要想创建出“真正的”文件流,至少有正反 2 种思路:
给普通流添上文件相关的 meta 信息
先拿到一个真正的文件流,再改掉其数据和 meta 信息
显然,前者更灵活一些,并且实现上能够做到完全不依赖文件
文件流的生产过程
沿着凭空创造的思路,探究 fs.createReadStream API 的 内部实现 之后发现,生产文件流的关键过程如下:
function ReadStream(path, options) { // 1.打开path指定的文件 if (typeof this.fd !== 'number') this.open(); } ReadStream.prototype.open = function() { fs.open(this.path, this.flags, this.mode, (er, fd) => { // 2.拿到文件描述符并持有 this.fd = fd; this.emit('open', fd); this.emit('ready'); // 3.开始流式读取数据 // read来自父类Readable,主要调用内部方法_read // ref: https://github.com/nodejs/node/blob/v10.16.3/lib/_stream_readable.js#L390 this.read(); }); }; ReadStream.prototype._read = function(n) { // 4.从文件中读取一个chunk fs.read(this.fd, pool, pool.used, toRead, this.pos, (er, bytesRead) => { let b = null; if (bytesRead > 0) { this.bytesRead += bytesRead; b = thisPool.slice(start, start + bytesRead); } // 5.(通过触发data事件)吐出一个chunk,如果还有数据,process.nextTick再次this.read,直至this.push(null)触发'end'事件 // ref: https://github.com/nodejs/node/blob/v10.16.3/lib/_stream_readable.js#L207 this.push(b); }); };
P.S.其中第 5 步相对复杂, this.push(buffer) 既能触发下一个 chunk 的读取( this.read() ),也能在数据读完之后(通过 this.push(null) )触发 'end' 事件,具体见 node/lib/_stream_readable.js
重新实现文件流
既然已经摸清了文件流的生产过程,下一步自然是 替换掉所有文件操作,直至文件流的实现完全不依赖文件 ,例如:
// 从文件中读取一个chunk fs.read(this.fd, pool, pool.used, toRead, this.pos, (er, bytesRead) => { /* ... */ }); // 换成 this._fakeReadFile(this.fd, pool, pool.used, toRead, this.pos, (bytesRead) => { /* ... */ }); // 从输入字符串对应的Buffer中copy出一个chunk ReadStream.prototype._fakeReadFile = function(_, buffer, offset, length, position, cb) { position = position || this.input._position; // fake read file async setTimeout(() => { let bytesRead = 0; if (position < this.input.byteLength) { bytesRead = this.input.copy(buffer, offset, position, position + length - 1); this.input._position += bytesRead; } cb(bytesRead); }, 0); }
即从中剔除文件操作,用基于字符串的操作去替代它们
四.解决方案
如此这般,就有了 ayqy/string-to-file-stream ,用来凭空创建文件流:
string2fileStream('string-content') === fs.createReadStream(/* path to a text file with content 'string-content' */)`
例如:
const string2fileStream = require('string-to-file-stream'); const input = 'Oh, my great data!'; const s = string2fileStream(input); s.on('data', (chunk) => { assert.equal(chunk.toString(), input); }); 生成的流同样能够具有文件 meta 信息: const string2fileStream = require('string-to-file-stream'); const formData = new FormData(); formData.append('file', string2fileStream('my-string-data', { path: './abc.txt' })); form.submit('example.org/upload', function(err, res) { console.log(res.statusCode); });
足够以假乱真
参考资料
fs.createReadStream(path[, options])
fs/streams.js
_stream_readable.js
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
更新日志
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]