Node.js从字符串生成文件流的实现方法

一.背景

在文件相关的数据加工等场景下,经常面临生成的物理文件应该如何处理的问题,比如:

生成的文件放到哪里,路径存在不存在?

临时文件何时清理,如何解决命名冲突,防止覆盖?

并发场景下的读写顺序如何保证?

……

对于读写物理文件带来的这些问题,最好的解决办法就是 不写文件 。然而,一些场景下想要不写文件可不那么容易,比如文件上传

二.问题

文件上传一般通过表单提交来实现,例如:

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解nodeJs文件系统(fs)与流(stream)

    一.简介 本文将介绍node.js文件系统(fs)和流(stream)的一些API已经参数使用情况. 二.目录 文件系统将介绍以下方法: 1.fs.readFile 2.fs.writeFile 3.fs.open 4.fs.read 5.fs.stat 6.fs.close 7.fs.mkdir 8.fs.rmdir 9.fs.readdir 10.fs.unlink stream流的四种类型readable,writable,duplex,transform以及stream对象的事件. 三.

  • Node.js从字符串生成文件流的实现方法

    一.背景 在文件相关的数据加工等场景下,经常面临生成的物理文件应该如何处理的问题,比如: 生成的文件放到哪里,路径存在不存在? 临时文件何时清理,如何解决命名冲突,防止覆盖? 并发场景下的读写顺序如何保证? -- 对于读写物理文件带来的这些问题,最好的解决办法就是 不写文件 .然而,一些场景下想要不写文件可不那么容易,比如文件上传 二.问题 文件上传一般通过表单提交来实现,例如: var FormData = require('form-data'); var fs = require('fs'

  • 利用node.js实现自动生成前端项目组件的方法详解

    本文主要给大家介绍了关于利用node.js实现自动生成前端项目组件的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: 脚本编写背景 写这个小脚本的初衷是,项目本身添加一个组件太繁琐了,比如我想要去建立一个login的组件,那么我需要手动去IDE中,创建index.js(组件出口文件),login.js(业务文件),login.html,login.less这四个文件.因为每个组件都有一些输出的代码,还要把之前组件的那几行拷贝过来,这种作业真的烦,于是乎写了一个小脚本去自动

  • Node.js 在本地生成日志文件的方法

    平常都使用console来打印 node 脚本执行时需要看到的信息,但这些信息也就只能在控制台查看.假如你希望将打印的信息输出到日志(log)文件查看的话,那就往下看看吧. 1.前言 期望: 每次运行脚本时,生成log日志存储到本地 每次执行脚本,之前日志内容清空 需要了解的知识点: fs new console.Console 2.什么是 fs 使用 node.js 对日志进行存储,就一定会对本地文件的增删改查,那么我们需要用到fs. 如果你写过 node,想必你应该见过它fs,fs全称为文件

  • Node.js中的缓冲与流模块详细介绍

    缓冲(buffer)模块 js起初就是为浏览器而设计的,所以能很好的处理unicode编码的字符串,但不能很好的处理二进制数据.这是Node.js的一个问题,因为Node.js旨在网络上发送和接收经常是以二进制格式传输的数据.比如: - 通过TCP连接发送和接收数据:  - 从图像或者压缩文件读取二进制数据:  - 从文件系统读写数据:  - 处理来自网络的二进制数据流 而Buffer模块为Node.js带来了一种存储原始数据的方法,于是可以再js的上下文中使用二进制数据.每当需要在Node.j

  • node.js express框架实现文件上传与下载功能实例详解

    本文实例讲述了node.js express框架实现文件上传与下载功能.分享给大家供大家参考,具体如下: 背景 昨天吉视传媒的客户对IPS信息发布系统又提了一个新需求,就是发布端发送消息时需要支持附件的上传,而接收端可以对发布端上传的附件进行下载:接收端回复消息时也需要支持上传附件,发布端可以对所有接收端上传的附件进行打包下载. 功能实现 前台部分 前台使用webUploader插件即可,这是百度开发的一款文件上传组件,具体使用查看它的API即可.这个项目之前开发的时候前台使用了angular.

  • Node.js path模块,获取文件后缀名操作

    我就废话不多说了,大家还是直接看代码吧~ demo.js: //path模块 var path=require('path'); /*nodejs自带的模块*/ var extname=path.extname("123.html"); //获取文件的后缀名 console.log(extname); 补充知识:node 的path模块中 path.resolve()和path.join()的区别 一.path模块的引入. 直接引用.node中自带的模块 const path = re

  • Node.js查找当前目录下文件夹实例代码

    整理文档,搜刮出Node.js查找当前目录下文件夹实例代码,稍微整理精简一下做下分享. var http = require("http"); var fs = require("fs"); var server = http.createServer(function (req,res) { //不处理收藏夹小图标 if(req.url == "/favicon.ico"){ return; } //files是文件名的数组 表示text这个文

  • 使用node.js对音视频文件加密的实例代码

    废话不多说了,直接给大家贴代码了,具体代码如下所示: fs.readFile('./downsuccess/'+name+'', {flag: 'r+', encoding: ''}, function (err, data) { console.log('读取中') if(err) { return; } let b = new Buffer(data); let c = b.toString('hex'); let cipherBuffer = _this.cipher(data); fs.

  • Node.JS枚举统计当前文件夹和子目录下所有代码文件行数

    使用Node.JS的大多数用记事本开发,有时侯会需要统计工程代码量,然后记事本大部分没有这个功能.其实用node.js几行代码就可以实现. var path = require('path') var fs = require('fs') //需要统计的文件类型,可自己删减,均小写 var codesFiles = [ '.css', '.js', '.html', '.tmpl', '.part', '.json', '.md', '.txt', '.yml', '.java', '.cs',

  • Node.js 中判断一个文件是否存在

    记录一些 Node.js 应用中的小知识点,如果你 Google/Baidu "Node.js 如何判断文件是否存在" 发现给出的很多答案还是使用的 fs.exists,这里不推荐使用 fs.exists 你可以选择 fs.stat 或 fs.access. 为什么不推荐 fs.exists 我们在设计一个回调函数时,通常会遵循一个原则 " 错误优先的回调函数",也就是返回值的第一个参数为错误信息,用以验证是否出错,其它的参数则用于返回数据. 如下所示为 fs.ex

随机推荐