Node.js的流
流可以看作是某段时间内从一个点移动到另一个点的数据序列。
Node.js中的流用于管理和处理数据,使用流完成对大量数据的操作以及逐段处理的操作。
流是Node.js中处理流式数据的抽象接口。
stream模块用于构建实现了流接口的对象
const stream = require(‘stream’);
stream模块主要用于开发人员创建新类型的流实例。
流的基本类型:
可写流(Writable)
可读流(Readable)
双工流(Duplex)
转换流(Transform)
流的实现类型
普通的流:基于字符串和Buffer(或Uint8Array)工作
对象模式的流:使用其他类型的JavaScript值工作
可写流和可读流都会在内部的缓冲器中存储数据,可缓冲的数据大小取决于传入流构造函数的highWaterMark选项(相当于水位线)。
双工流和转换流都是可读又可写的,各自维护着两个相互独立的内部缓冲器用于读取和写入。在维护数据流时,读取和写入两端可以各自独立地工作。
可读流
可读流是对提供数据的来源的一种抽象。所有可读流都实现了stream.Readable类定义的接口。
可读流有两种模式:流动(Flowing)和暂停(Paused)。
stream.Readable类定义的主要事件
- data:当有数据可读时被触发。
- end:没有更多的数据可读时被触发。
- close:当流或其底层资源被关闭时被触发。
stream.Readable类定义的主要方法
- readable.read([size]):从内部缓冲区拉取并返回数据。
- readable.pause():使流动模式的流停止触发data事件,并切换出流动模式。
- readable.setEncoding(encoding):为从可读流读取的数据设置字符编码。
可读流操作示例
可读流操作示例
const fs = require('fs')
//以流的方式读取文件
var readStream=fs.createReadStream('demo.txt');
var str='';//保存数据
readStream.on('data',function(chunk){
str+=chunk;
})
//读取完成
readStream.on('end',function(chunk){
console.log(str);
})
//读取失败
readStream.on('error',function(err){
console.log(err);
})
可写流
可写流是对数据被写入的目的地的一种抽象。
所有可写流都实现了stream.Writable类定义的接口。
stream.Writable类定义的主要事件
close:当流或其底层资源被关闭时被触发。
error:写入数据发生错误时被触发。
finish:调用stream.end()方法且缓冲数据都已传给底层系统之后被触发。
stream.Writable类定义的主要方法
writable.write(chunk[, encoding][, callback]):写入数据到流,并在数据被完全处理之后调用回调函数。
可写流操作示例
const fs = require('fs')
var str = '这首歌真的很好听呢';
// 创建一个可以写入的流,写入到文件output.txt 中
var writerStream = fs.createWriteStream('output.txt');
// 使用 utf8 编码写入数据
writerStream.write(str,'UTF8');
// 标记文件末尾
writerStream.end();
// 处理流事件
writerStream.on('finish', function() {
console.log('写入完成!');
});
writerStream.on('error', function(err){
console.log('写入失败');
});
管道读写操作
管道操作将从一个流中获取的数据传递到另外一个流中。
可读流提供的readable.pipe()方法在可读流与可写流之间架起桥梁
readable.pipe(destination[, options])
将可读流的所有数据通过管道推送到文件的示例
const readable = getReadableStreamSomehow();//创建一个可读流
const writable = fs.createWriteStream('file.txt'); // 创建一个可写流
readable.pipe(writable); //管道读写操作,将readable 的所有数据都推送到文件file.txt
在单个可读流上绑定多个可写流,对流进行链式管道操作的示例
const fs = require('fs');
const r = fs.createReadStream('file.txt');
const z = zlib.createGzip();
const w = fs.createWriteStream('file.txt.gz');
r.pipe(z).pipe(w);// 链式管道操作两个可写流
默认当来源可读流触发end事件时,目标可写流也会调用stream.end()结束写入。
禁用默认行为,end选项设为false,目标流就会保持打开状态
reader.pipe(writer, { end: false });
reader.on('end', () => {
writer.end('结束');
});
可读流发生错误,目标可写流不会自动关闭,需要手动关闭所有流以避免内存泄漏。
readable.unpipe()方法用于解绑之前使用stream.pipe()方法绑定的可写流。
提供图片浏览服务
搭建B/S架构
应用程序基于B/S架构,由Node.js内置的核心模块http提供Web服务。
const server = http.createServer(function(req, res){
//请求处理并返回结果
});
server.listen(8000);
解析资源文件路径
获取请求的URL并返回文件路径名
var pathName = url.parse(req.url).pathname;
从文件路径名取得资源文件的绝对路径需要用到全局变量__dirname
var filePath = path.resolve(__dirname + pathName);
可以通过path.extname属性来获取文件的扩展名来确定文件类型
处理不同类型的资源文件
Content-Type的格式:
Content-Type:type/subtype ;parameter
由一个键值对集合来指定不同扩展名与Content-Type类型之间的对应关系
var mime = {
“.jpeg”: “image/jpeg”,
“.jpg”: “image/jpeg”,
}
文件读取
读取文件的状态
fs.stat(path,callback)
资源文件可以使用fs.createReadStream()方法打开。
静态文件服务器通常使用gzip压缩文件以提高传输效率,内置模块zlib提供gzip压缩功能。
可以在文件流发送到HTTP响应之前增加一个压缩文件的管道操作:
stream.pipe(zlib.createGzip()).pipe(res);
让浏览器知道已经开启gzip压缩,需要在HTTP消息头中提供相应的内容编码信息:
res.writeHead(200, { “content-encoding”: ‘gzip’ });
代码如下
//1
const http = require('http'); //加载http模块
const path = require('path');
const fs = require('fs')
const url = require('url')
var curDir = ''; //当前目录名
//创建HTTP服务
const server = http.createServer(function (req, res) {
//定义mime对象设置相应的响应头类型,这里仅列出少量的扩展名用于测试
var mime = {
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".png": "image/png",
".tiff": "image/tiff",
".pdf": "application/pdf"
};
//获取请求URL并转换请求路径
var pathName = url.parse(req.url).pathname;
//获取请求URL并转换请求路径
//2
//对路径进行解码以防中文乱码
var pathName = decodeURI(pathName);
//获取资源文件的绝对路径,这里用到全局变量__dirname
var filePath = path.resolve(__dirname + pathName);
console.log(filePath); //控制台显示绝对路径
//获取文件的扩展名
var extName = path.extname(pathName);
//为简化处理,没有扩展名的或未知类型使用text/plain表示
var contentType = mime[extName] || "text/plain";
//3
//通过读取文件状态来决定如何读取静态文件
fs.stat(filePath, function (err, stats) {
if(err){
res.writeHead(404,{'content-type':'text/html'})
res.end("<h1>404 没有找到</h1>")
}
//文件存在且没有错误
if(!err && stats.isFile()){
readFile(filePath,contentType);
}
//如果路径是目录
if (!err && stats.isDirectory()) {
var html = "<head><meta charset = 'utf-8'/></head><body><ul>";
curDir = path.basename(path.relative(__dirname, filePath)); //获取当前目录
fs.readdir (filePath,(err,files) =>{
if(err){
console.log('读取路径失败');
}else{
for (var file of files) {
//这里用到了ES6模板字符串
var curPath = path.join(curDir, file);
html += `<li><a href='${filecurPath}'>${file}</a></li>`;
}
html += '</ul></body>';
res.writeHead(200, {
'content-type': "text/html"
});
res.end(html);
}
});
}
//4
//声明函数流式读取文件
function readFile(filePath, contentType) {
//设置HTTP消息头
res.writeHead(200, {
'content-type': contentType,
'content-encoding': 'gzip'
});
//创建流对象读取文件
var stream = fs.createReadStream(filePath);
//流式读取错误处理
stream.on('error', function () {
res.writeHead(500, {
'content-type': contentType
});
res.end("<h1>500 服务器错误</h1>");
});
//链式管道操作将文件内容流到客户端
stream.pipe(zlib.createGzip()).pipe(res);
}
});
});
//5
var port = 8000; //指定服务器监听的接口
server.listen(port, function () {
console.log(`图片服务器正运行在端口:${port}`);
console.log(`访问网址: http://localhost:${port}`);
});