07 【nodejs内置模块(下)】

1.stream 模块

stream是Node.js提供的又一个仅在服务区端可用的模块,目的是支持“流”这种数据结构。

什么是流?流是一种抽象的数据结构。想象水流,当在水管中流动时,就可以从某个地方(例如自来水厂)源源不断地到达另一个地方(比如你家的洗手池)。我们也可以把数据看成是数据流,比如你敲键盘的时候,就可以把每个字符依次连起来,看成字符流。这个流是从键盘输入到应用程序,实际上它还对应着一个名字:标准输入流(stdin)。

如果应用程序把字符一个一个输出到显示器上,这也可以看成是一个流,这个流也有名字:标准输出流(stdout)。流的特点是数据是有序的,而且必须依次读取,或者依次写入,不能像Array那样随机定位。

有些流用来读取数据,比如从文件读取数据时,可以打开一个文件流,然后从文件流中不断地读取数据。有些流用来写入数据,比如向文件写入数据时,只需要把数据不断地往文件流中写进去就可以了。

在Node.js中,流也是一个对象,我们只需要响应流的事件就可以了:data事件表示流的数据已经可以读取了,end事件表示这个流已经到末尾了,没有数据可以读取了,error事件表示出错了。

1.1 读取流

const fs = require('fs');//创建读取流
let rs = fs.createReadStream('hello.txt', 'utf-8');rs.on('open', function () {console.log('读取的文件已打开');
}).on('close', function () {console.log('读取流结束');
}).on('error', err => {console.log(err);
}).on('data', function (chunk) {//每一批数据流入完成console.log('单批数据流入:' + chunk.length);console.log(chunk);
});

要注意,data事件可能会有多次,每次传递的chunk是流的一部分数据。

读取视频

const fs = require('fs');//创建读取流
let rs = fs.createReadStream('video.mp4');//每一批数据流入完成
rs.on('data', function (chunk) {console.log('单批数据流入:' + chunk.length);console.log(chunk);
});

1.2 写入流

要以流的形式写入文件,只需要不断调用write()方法,最后以end()结束:

const fs = require('fs');//创建写入流
let ws = fs.createWriteStream('hello.txt', 'utf-8');//监听文件打开事件
ws.on('open', function () {console.log('文件打开');
});//监听文件关闭事件
ws.on('close', function () {console.log('文件写入完成,关闭');
});//文件流式写入
ws.write('helloworld1!', function (err) {if (err) {console.log(err);} else {console.log('内容1流入完成');}
});
ws.write('helloworld2!', function (err) {if (err) {console.log(err);} else {console.log('内容2流入完成');}
});//文件写入完成
ws.end(function () {console.log('文件写入关闭');
});

pipe 就像可以把两个水管串成一个更长的水管一样,两个流也可以串起来。一个Readable流和一个Writable流串起来后,所有的数据自动从Readable流进入Writable流,这种操作叫pipe

在Node.js中,Readable流有一个pipe()方法,就是用来干这件事的。

让我们用pipe()把一个文件流和另一个文件流串起来,这样源文件的所有数据就自动写入到目标文件里了,所以,这实际上是一个复制文件的程序:

const fs = require('fs');//创建读取流
let rs = fs.createReadStream('video.mp4');
let ws = fs.createWriteStream('b.mp4');rs.on('close', function () {console.log('读取流结束');
});rs.pipe(ws);

pipe原理

const fs = require('fs');//创建读取流
let rs = fs.createReadStream('video.mp4');
let ws = fs.createWriteStream('b.mp4');rs.on('close', function () {ws.end();console.log('读取流结束');
});//每一批数据流入完成
rs.on('data', function (chunk) {console.log('单批数据流入:' + chunk.length);ws.write(chunk, () => {console.log('单批输入流入完成');});
});

2.资源压缩模块 zib

2.1 概览

做过web性能优化的同学,对性能优化大杀器gzip应该不陌生。浏览器向服务器发起资源请求,比如下载一个js文件,服务器先对资源进行压缩,再返回给浏览器,以此节省流量,加快访问速度。

浏览器通过HTTP请求头部里加上Accept-Encoding,告诉服务器,“你可以用gzip,或者defalte算法压缩资源”。

Accept-Encoding:gzip, deflate

那么,在nodejs里,是如何对资源进行压缩的呢?答案就是Zlib模块。=

2.2 压缩的例子

非常简单的几行代码,就完成了本地文件的gzip压缩。

var fs = require('fs');
var zlib = require('zlib');var gzip = zlib.createGzip();var readstream = fs.createReadStream('./extra/fileForCompress.txt');
var writestream = fs.createWriteStream('./extra/fileForCompress.txt.gz');readstream.pipe(gzip).pipe(writestream);

2.3 解压的例子

同样非常简单,就是个反向操作。

var fs = require('fs');
var zlib = require('zlib');var gunzip = zlib.createGunzip();var readstream  = fs.createReadStream('./extra/fileForCompress.txt.gz');
var writestream  = fs.createWriteStream('./extra/fileForCompress1.txt');readstream.pipe(gunzip).pipe(writestream);

2.4 服务端gzip压缩

首先判断 是否包含 accept-encoding 首部,且值为gzip

  • 否:返回未压缩的文件。
  • 是:返回gzip压缩后的文件。
var http = require('http');
var zlib = require('zlib');
var fs = require('fs');
var filepath = './extra/fileForGzip.html';var server = http.createServer(function(req, res){var acceptEncoding = req.headers['accept-encoding'];var gzip;if(acceptEncoding.indexOf('gzip')!=-1){ // 判断是否需要gzip压缩gzip = zlib.createGzip();// 记得响应 Content-Encoding,告诉浏览器:文件被 gzip 压缩过res.writeHead(200, {'Content-Encoding': 'gzip'});fs.createReadStream(filepath).pipe(gzip).pipe(res);}else{fs.createReadStream(filepath).pipe(res);}});server.listen('3000');

将js大文件返回

const fs = require('fs');
const zlib = require('zlib');//这两个要写在fs模块后面
const gzip = zlib.createGzip();
const http = require('http');http.createServer((req, res) => {let rs = fs.createReadStream('hello.js');res.writeHead(200, {'Content-Type': 'application/x-javascript;charset=utf-8','Content-Encoding': 'gzip',});rs.pipe(gzip).pipe(res);}).listen(3000, () => {console.log('server start');});

2.5 服务端字符串gzip压缩

代码跟前面例子大同小异。这里采用了 zlib.gzipSync(str) 对字符串进行gzip压缩。

var http = require('http');
var zlib = require('zlib');var responseText = 'hello world';var server = http.createServer(function(req, res){var acceptEncoding = req.headers['accept-encoding'];if(acceptEncoding.indexOf('gzip')!=-1){res.writeHead(200, {'content-encoding': 'gzip'});res.end(zlib.gzipSync(responseText) );}else{res.end(responseText);}});server.listen('3000');

3.数据加密模块 crypto

crypto模块的目的是为了提供通用的加密和哈希算法。用纯JavaScript代码实现这些功能不是不可能,但速度会非常慢。Nodejs用C/C++实现这些算法后,通过cypto这个模块暴露为JavaScript接口,这样用起来方便,运行速度也快。

3.1 hash例子

hash.digest([encoding]):计算摘要。encoding可以是hexlatin1或者base64。如果声明了encoding,那么返回字符串。否则,返回Buffer实例。注意,调用hash.digest()后,hash对象就作废了,再次调用就会出错。

hash.update(data[, input_encoding]):input_encoding可以是utf8ascii或者latin1。如果data是字符串,且没有指定 input_encoding,则默认是utf8。注意,hash.update()方法可以调用多次。

var crypto = require('crypto');
var fs = require('fs');var content = fs.readFileSync('./test.txt', {encoding: 'utf8'});
var hash = crypto.createHash('sha256');
var output;hash.update(content);output = hash.digest('hex'); console.log(output);
// 输出内容为:
// b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9

也可以这样:

var crypto = require('crypto');
var fs = require('fs');var input = fs.createReadStream('./test.txt', {encoding: 'utf8'});
var hash = crypto.createHash('sha256');hash.setEncoding('hex');input.pipe(hash).pipe(process.stdout)// 输出内容为:
// b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9

hash.digest()后,再次调用digest()或者update()

var crypto = require('crypto');
var fs = require('fs');var content = fs.readFileSync('./test.txt', {encoding: 'utf8'});
var hash = crypto.createHash('sha256');
var output;hash.update(content);
hash.digest('hex'); // 报错:Error: Digest already called
hash.update(content);// 报错:Error: Digest already called
hash.digest('hex');

3.2 HMAC例子

HMAC的全称是Hash-based Message Authentication Code,也即在hash的加盐运算。

具体到使用的话,跟hash模块差不多,选定hash算法,指定“盐”即可。

例子1:

var crypto = require('crypto');
var fs = require('fs');var hmac = crypto.createHmac('sha256', 'secret');
var input = fs.readFileSync('./test.txt', {encoding: 'utf8'});hmac.update(input);console.log( hmac.digest('hex') );
// 输出:
// 734cc62f32841568f45715aeb9f4d7891324e6d948e4c6c60c0621cdac48623a

例子2:

var crypto = require('crypto');
var fs = require('fs');var hmac = crypto.createHmac('sha256', 'secret');
var input = fs.createReadStream('./test.txt', {encoding: 'utf8'});hmac.setEncoding('hex');input.pipe(hmac).pipe(process.stdout)
// 输出:
// 734cc62f32841568f45715aeb9f4d7891324e6d948e4c6c60c0621cdac48623a

3.3 MD5例子

MD5(Message-Digest Algorithm)是计算机安全领域广泛使用的散列函数(又称哈希算法、摘要算法),主要用来确保消息的完整和一致性。常见的应用场景有密码保护、下载文件校验等。

特点

  1. 运算速度快:对jquery.js求md5值,57254个字符,耗时1.907ms
  2. 输出长度固定:输入长度不固定,输出长度固定(128位)。
  3. 运算不可逆:已知运算结果的情况下,无法通过通过逆运算得到原始字符串。
  4. 高度离散:输入的微小变化,可导致运算结果差异巨大。
  5. 弱碰撞性:不同输入的散列值可能相同。

应用场景

  1. 文件完整性校验:比如从网上下载一个软件,一般网站都会将软件的md5值附在网页上,用户下载完软件后,可对下载到本地的软件进行md5运算,然后跟网站上的md5值进行对比,确保下载的软件是完整的(或正确的)
  2. 密码保护:将md5后的密码保存到数据库,而不是保存明文密码,避免拖库等事件发生后,明文密码外泄。
  3. 防篡改:比如数字证书的防篡改,就用到了摘要算法。(当然还要结合数字签名等手段)
var crypto = require('crypto');
var md5 = crypto.createHash('md5');var result = md5.update('a').digest('hex');// 输出:0cc175b9c0f1b6a831c399e269772661
console.log(result);

3.4 例子:密码保护

前面提到,将明文密码保存到数据库是很不安全的,最不济也要进行md5后进行保存。比如用户密码是123456,md5运行后,得到输出:e10adc3949ba59abbe56e057f20f883e

这样至少有两个好处:

  1. 防内部攻击:网站主人也不知道用户的明文密码,避免网站主人拿着用户明文密码干坏事。
  2. 防外部攻击:如网站被黑客入侵,黑客也只能拿到md5后的密码,而不是用户的明文密码。

示例代码如下:

var crypto = require('crypto');function cryptPwd(password) {var md5 = crypto.createHash('md5');return md5.update(password).digest('hex');
}var cryptedPassword = cryptPwd('123456');console.log(cryptedPassword);
// 输出:e10adc3949ba59abbe56e057f20f883e

单纯对密码进行md5不安全

前面提到,通过对用户密码进行md5运算来提高安全性。但实际上,这样的安全性是很差的,为什么呢?

稍微修改下上面的例子,可能你就明白了。相同的明文密码,md5值也是相同的。

var crypto = require('crypto');function cryptPwd(password) {var md5 = crypto.createHash('md5');return md5.update(password).digest('hex');
}console.log( cryptPwd('123456') );
// 输出:e10adc3949ba59abbe56e057f20f883econsole.log( cryptPwd('123456') );
// 输出:e10adc3949ba59abbe56e057f20f883e

也就是说,当攻击者知道算法是md5,且数据库里存储的密码值为e10adc3949ba59abbe56e057f20f883e时,理论上可以可以猜到,用户的明文密码就是123456

事实上,彩虹表就是这么进行暴力破解的:事先将常见明文密码的md5值运算好存起来,然后跟网站数据库里存储的密码进行匹配,就能够快速找到用户的明文密码。(这里不探究具体细节)

那么,有什么办法可以进一步提升安全性呢?答案是:密码加盐。

密码加盐

“加盐”这个词看上去很玄乎,其实原理很简单,就是在密码特定位置插入特定字符串后,再对修改后的字符串进行md5运算。

例子如下。同样的密码,当“盐”值不一样时,md5值的差异非常大。通过密码加盐,可以防止最初级的暴力破解,如果攻击者事先不知道”盐“值,破解的难度就会非常大。

var crypto = require('crypto');function cryptPwd(password, salt) {// 密码“加盐”var saltPassword = password + ':' + salt;console.log('原始密码:%s', password);console.log('加盐后的密码:%s', saltPassword);// 加盐密码的md5值var md5 = crypto.createHash('md5');var result = md5.update(saltPassword).digest('hex');console.log('加盐密码的md5值:%s', result);
}cryptPwd('123456', 'abc');
// 输出:
// 原始密码:123456
// 加盐后的密码:123456:abc
// 加盐密码的md5值:51011af1892f59e74baf61f3d4389092cryptPwd('123456', 'bcd');
// 输出:
// 原始密码:123456
// 加盐后的密码:123456:bcd
// 加盐密码的md5值:55a95bcb6bfbaef6906dbbd264ab4531

07 【nodejs内置模块(下)】相关推荐

  1. 9.nodejs 内置模块

    nodejs 内置模块 文件操作 在 nodejs 中,提供了 fs 模块,来供我们操作文件.在 nodejs 中,操作文件都提供了同步和异步的方式,但是在实际的开发中,我们更多的还是使用异步来进行开 ...

  2. 解决nodejs环境下端口号被占用的方法

    解决nodejs环境下端口号被占用的方法 参考文章: (1)解决nodejs环境下端口号被占用的方法 (2)https://www.cnblogs.com/guoliangstar/p/1014930 ...

  3. nodejs window下安装与配置淘宝镜像

    nodejs window下安装与配置淘宝镜像 1,前往nodejs官网下载安装软件,地址:https://nodejs.org/en/ 2,点击下一步继续安装,安装完成,在命令输入:node -v, ...

  4. 毕设(一):基于WebGL(Cesium+MongoDB+NodeJS)下的三维城市室内外场景可视化+属性管理

    系列文章目录 毕设(一):基于WebGL(Cesium+MongoDB+NodeJS)下的三维城市室内外场景可视化+属性管理 文章目录 系列文章目录 前言 一.前期准备 二.创建express工程项目 ...

  5. Nodejs 内置模块的基本使用

    内置模块的基本使用 // 01. 导包 const fs = require("fs"); // 02. 调用unlink删除方法 // 第一个参数:要删除的文件的路径 // 第二 ...

  6. nodejs cluster_NodeJS下好用的Redis客户端ioredis,再推荐一个Redis可视化工具

    介绍 ioredis是一个适用于Nodejs的Redis全功能客户端,健壮性以及高性能是它的亮点,支持Redis> = 2.6.12和(Node.js> = 6),ioredis是一个功能 ...

  7. 07 | 链表(下):如何轻松写出正确的链表代码?

    目录 技巧一:理解指针或者引用的含义 技巧二:警惕指针丢失和内存泄漏 技巧三:利用哨兵简化实现难度 技巧四:重点留意边界条件处理 技巧五:举例画图,辅助思考 技巧六:多写多练,没有捷径 链表的概念回顾 ...

  8. 野生葫芦娃用心写的nodejs 内置模块------文件操作

    文件操作 Node中的文件系统: 文件具体操作 文件的写入 fs.writeFile() 文件的追加 fs.appendFile() 文件的读取 fs.readFile() 文件的复制 fs.copy ...

  9. 07.保护模式下字符显示

    简介 上一博文我们实现了从实模式到保护模式的切换并在屏幕上显示了简单的字符,对于保护模式给我们带来的寻址范围的变化体会可能不深入,很难体会在保护模式下的地址寻址变化. 目标 在保护模式下将一段字符串复 ...

最新文章

  1. 《阿里云前端技术周刊》第五期
  2. 梯度下降法优化目标函数_如何通过3个简单的步骤区分梯度下降目标函数
  3. 普通人为什么要学习Python
  4. uc点网页显示服务器升级,让uc浏览器网页加载速度提升100%
  5. mtl库在GCC编译器下的使用
  6. 基于OpenCV的计算机视觉入门(5)图像美化(上)
  7. Android 完美高仿的微信源码
  8. xpath 解析离线网页解析本地网页解析本体html文件
  9. 2019最新盘点:适合中小型企业的财务系统软件
  10. 必看!超详细的电子元器件选型经验分享
  11. 控制系统设计专题(一)——PID控制算法
  12. 开关电源损耗分析 以Buck为例
  13. python等比例压缩图片_python使用pil进行图像处理(等比例压缩、裁剪)实例代码
  14. asp.net2.0(C#)图像处理类[转]
  15. “Entity Data Modle Designer 无法显示”的问题
  16. [WTL/ATL]_[Gdiplus]_[绘制虚线并设置破折号空格的宽度]
  17. 车机软件测试ADBShell命令集合
  18. 服务器怎么使用无线网卡,无线上网卡怎么用
  19. shopee上架接口java_Shopee虾皮店小秘ERP刊登发布产品图文教程
  20. 体系 英文缩写是(D)

热门文章

  1. 国内外远程办公软件现状
  2. python元组 列表 字符串最后一个下标_python字符串列表元组序列操作
  3. Python爬虫神器简单介绍与使用(requests、Beautiful Soup、selenium等)
  4. 微信小程序支付(java后端)
  5. Spark源码编译(windows)
  6. 电子信息工程的就业方向是怎样的?
  7. java连接打印机_JAVA实现连接本地打印机并打印文件的实现代码
  8. win10开启上帝模式
  9. html5+css+js简单了解
  10. 【问题及解决】Elasticsearch不能正常启动do not exists on master, act as master failure