Node.js构建HTTP文件服务器实战:fs与EventEmitter详解

作者:栖木

时间:2026-10-22

浏览:456

本文通过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内置模块提供的事件用法完全一致。

以上即为本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章