Node.js构建HTTP文件服务器实战:fs与EventEmitter详解
本文通过Node.js实现一个简易HTTP文件服务器,详解如何结合fs模块与Stream处理文件读取。重点解析fs.stat获取文件状态、createReadStream分块传输数据,以及利用EventEmitter处理data、end等事件,帮助开发者掌握异步IO与事件驱动编程在文件服务中的应用。
Node.js构建HTTP文件服务器实战:fs与EventEmitter详解
HelloWorld示例仅具演示价值,本文将展示一个更具实用性的案例:构建文件服务器。我们将利用Node.js搭建基于HTTP协议的文件服务器,允许用户通过浏览器或其他下载工具从该服务器获取文件。
实现文件读取功能需引入File System模块(即“fs”),同时结合Stream流处理。此外,还需解析URL以区分HTTP请求方法,并借助EventEmitter处理异步事件。
以下是文件服务器FileServer的核心代码实现。
首先展示完整代码,结构相对简洁:
// 引入http模块
var http = require("http");
var fs = require("fs");
// 创建server,指定处理客户端请求的函数
http.createServer(
function(request, response) {
//判断HTTP方法,只处理GET
if(request.method != "GET"){
response.writeHead(403);
response.end();
return null;
}
//此处也可使用URL模块来分析URL(https://nodejs.org/api/url.html)
var sep = request.url.indexOf('?');
var filePath = sep < 0 ? request.url : request.url.slice(0, sep);
console.log("GET file: " + filePath);
//当文件存在时发送数据给客户端,否则404
var fileStat = fs.stat("."+filePath,
function(err, stats){
if(err) {
response.writeHead(404);
response.end();
return null;
}
//TODO:Content-Type应该根据文件类型设置
response.writeHead(200, {"Content-Type": "text/plain", "Content-Length": stats.size});
//使用Stream
var stream = fs.createReadStream("."+filePath);
stream.on('data',function(chunk){
response.write(chunk);
});
stream.on('end',function(){
response.end();
});
stream.on('error',function(){
response.end();
});
}
);
}
).listen(8000);
console.log("Hello World start listen on port 8000");
代码中最大的变化在于传递给createServer方法的回调函数逻辑。
我们首先检查request.method,若非GET请求则直接返回403状态码。若是GET请求,则进一步判断目标文件是否存在:若不存在返回404,若存在则读取文件内容并发送给客户端。整体逻辑清晰简洁,接下来我们将深入介绍其中涉及的新知识点。
File System模块详解
File System模块详解
使用FileSystem前,需通过require引入fs模块,如上述代码所示。File System的API文档非常详尽,可参考官方链接:https://nodejs.org/api/fs.html。此处仅讲解本文用到的关键特性。
获取文件状态
在FileServer中,收到客户端请求后,首先通过fs.stat()方法获取文件状态。fs.stat()方法的函数原型如下:
fs.stat(path, callback)
该方法接受两个参数:第一个是文件路径,第二个是回调函数。fs.stat()为异步操作,结果通过回调函数callback返回。callback函数的原型定义如下:
function(err, stats)
回调函数的第一个参数指示是否发生错误;第二个参数是一个fs.Stats类型的对象,包含文件的状态信息,如文件大小、创建时间和修改时间等。
FileServer代码在获取文件状态后,读取文件大小,并调用http.ServerResponse的writeHead方法,将HTTP状态码设置为200,同时设置Content-Length头部信息。具体代码如下:
代码如下:
response.writeHead(200, {"Content-Type": "text/plain", "Content-Length": stats.size})ReadStream流处理
ReadStream流处理
随后,代码调用fs.createReadStream创建了一个ReadStream对象。该对象既是Stream流,也是EventEmitter事件发射器。
fs.createReadStream方法的函数原型如下:
fs.createReadStream(path[, options])
第一个参数为文件路径,第二个参数是可选的JSON对象,用于指定打开文件的选项,其默认值如下:
{ flags: ‘r',
encoding: null,
fd: null,
mode: 0666,
autoClose: true
}
autoClose属性默认为true,当文件读取完毕或发生错误时,文件句柄会自动关闭。fd属性可关联一个已有的文件描述符,若提供此属性则忽略path参数,直接基于已打开的文件创建流。options还可包含start和end项,用于指定读取文件的起始和结束位置,这对于实现断点续传功能至关重要,用法示例如下:
fs.createReadStream('sample.mp4', {start: 1000, end: 10000});
encoding参数用于指定文件编码,对文本文件尤为重要,目前支持'utf8'、'ascii'和'base64'三种编码格式。
ReadStream采用异步方式分块读取数据,每读取一部分数据便会触发data事件,并将数据传递给与该事件关联的监听器(即回调方法)。在我们的示例中,直接调用response.write将数据发送给客户端。需注意,response.write可能会被多次调用。由于我们已设置Content-Length头部,因此不会采用chunked编码方式;若未设置Content-Length,则默认启用chunked编码。
当ReadStream完成文件读取时,会触发end事件;若发生错误,则触发error事件。我们监听这两个事件以简单地终止响应。
在示例代码中可见stream.on这种写法,下面对此进行详细解释。
EventEmitter事件机制
EventEmitter事件机制
Node.js基于V8引擎实现了事件驱动的IO模型,这是其最显著且优秀的特性之一。借助事件机制,可以充分利用异步IO突破单线程编程模型的性能瓶颈,从而使JavaScript能够胜任后端开发任务。
EventEmitter基本用法
events.EventEmitter是一个简单的事件发射器实现,提供了addListener、on、once、removeListener、emit等方法,开发者可以方便地调用这些API来监听或发射特定事件。
我们在示例中使用的fs.ReadStream即为一个EventEmitter实例,它实现了stream.Readable接口,该接口包含data、error、end、close、readable等事件。
通常,我们使用EventEmitter的on或addListener方法来监听事件,这些事件可能会被多次触发,每次触发时,我们提供的回调方法都会被执行。示例代码正是采用了这种方式:
stream.on('data',function(chunk){
response.write(chunk);
});
Node.js的事件机制允许为某个事件关联多个回调方法列表,使得多个观察者可以监听同一事件。每次事件发射时,可能携带数据和状态信息,这些数据通过回调方法的参数传递。特定事件对应的回调方法参数形式,由定义该事件的类(实例)决定。EventEmitter的emit方法原型如下:
emitter.emit(event[, arg1][, arg2][, ...])
该原型表明,事件的回调方法可以接受一个或多个参数,也可以无参数。若要了解某个事件回调方法是否有参数及其具体含义,需查阅相关API文档。例如,stream.Readable的data事件参数为chunk,类型为Buffer,代表读取到的数据块。
若只需监听某个事件一次,可调用EventEmitter的once方法。若要移除特定事件的监听器,可调用removeListener;若要移除所有监听器,则调用removeAllListener。
自定义事件实现
Node.js的许多模块都继承自Event模块。我们也可以继承EventEmitter来实现自定义对象,并添加自定义事件。
以下是一个简单的示例:
var util=require("util");
var events = require("events");
function Ticker() {
var self = this;
events.EventEmitter.call(this);
setInterval(function(){
self.emit("tick")
},
1000
);
}
util.inherits(Ticker, events.EventEmitter);
var ticker = new Ticker();
ticker.on("tick", function() {
console.log("tick event");
});
在该示例中,我们定义了Ticker对象,通过全局方法setInterval开启定时器,每隔1000毫秒发射一个名为“tick”的事件。
Node.js的工具模块封装了继承方法,我们调用util.inherits方法来完成Ticker对events.EventEmitter的继承。
自定义事件的使用方法,与Node.js内置模块提供的事件用法完全一致。
以上即为本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
NestJS 是什么?与 Express、Koa 的核心区别及选型建议
2026-11-05 13:46:41
生产环境升级 Node.js 的注意事项与兼容性测试策略
2026-11-05 09:19:24
nvm 常用命令详解:怎么平滑切换和升级 Node.js 版本
2026-11-04 09:02:34
JavaScript和JSP的根本区别:运行环境与执行机制解析
2026-11-03 23:30:18
使用nvm卸载指定版本Node.js并切换默认环境
2026-11-03 23:09:26
