Node.js创建HTTP文件服务器的使用示例


Posted in Javascript onMay 11, 2018

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。如果是呢,就判断文件是否存在,不存在,返回404,存在就读取数据写给客户端。逻辑就是这么简单。下面我们来介绍用到的新知识。

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

接下来呢,我们调用fs.createReadStream创建了一个ReadStream对象。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事件,数据呢,会传递给与事件关联的listener(实际上是一个回调方法)。在我们的代码里,仅仅是调用response.write把数据写给客户端。注意,可能会多次调用response.write哦。又因为我们设置了Content-Length,所以不会采用chunked编码方式。如果我们不设置Content-Length,那默认会启用chunked方式。

ReadStream读完文件时会发射end事件,出错时会发射error事件,我们监听这两个事件,简单的终止响应。

我们在示例代码中看到了stream.on这种代码,下面来解释吧。

EventEmitter

Node.js基于V8引擎实现的事件驱动IO,是其最大最棒的特色之一。有了事件机制,就可以充分利用异步IO突破单线程编程模型的性能瓶颈,使得用JavaScript作后端开发有了实际意义。

EventEmitter的基本用法

events.EventEmitter是一个简单的事件发射器的实现,具有addListener、on、once、removeListener、emit等方法,开发者可以很方便的调用这些API监听某个事件或者发射某个事件。

我们在示例中用到的fs.ReadStream就是一个EventEmitter,它实现了stream.Readable接口,而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的工具模块封装了继承的方法,我们调用它来的inherits方法来完成Ticker对events.EventEmitter的继承。

自定义事件的使用方法,和Node.js内置模块提供的事件的用法完全一样。

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

Javascript 相关文章推荐
利用jquery动画特效和css打造的侧边弹出垂直导航
Apr 04 Javascript
jQuery实现瀑布流布局
Dec 12 Javascript
分享两个手机访问pc网站自动跳转手机端网站代码
Dec 24 Javascript
jQuery实现移动 和 渐变特效的点击事件
Feb 26 Javascript
jquery实现网页的页面平滑滚动效果代码
Nov 02 Javascript
jquery的父、子、兄弟节点查找,节点的子节点循环方法
Dec 07 Javascript
微信小程序 image组件binderror使用例子与js中的onerror区别
Feb 15 Javascript
移动端触屏幻灯片图片切换插件idangerous swiper.js
Apr 10 Javascript
vue中使用props传值的方法
May 08 Javascript
vue spa应用中的路由缓存问题与解决方案
May 31 Javascript
Javascript数组方法reduce的妙用之处分享
Jun 10 Javascript
vue实现禁止浏览器记住密码功能的示例代码
Feb 03 Vue.js
Node.js引入UIBootstrap的方法示例
May 11 #Javascript
Node.js使用Angular简单示例
May 11 #Javascript
Node.js 使用AngularJS的方法示例
May 11 #Javascript
Angular使用动态加载组件方法实现Dialog的示例
May 11 #Javascript
详解JavaScript中的数组合并方法和对象合并方法
May 11 #Javascript
Node.js使用cookie保持登录的方法
May 11 #Javascript
实例解析Vue.js下载方式及基本概念
May 11 #Javascript
You might like
融入意大利的咖啡文化
2021/03/03 咖啡文化
PHP 采集程序中常用的函数
2009/12/09 PHP
PHP网站建设的流程与步骤分享
2015/09/25 PHP
PHP弹出对话框技巧详细解读
2015/09/26 PHP
PHP实现批量清空删除指定文件夹所有内容的方法
2017/05/30 PHP
ThinkPHP5.1表单令牌Token失效问题的解决
2019/03/22 PHP
JavaScript 类的定义和引用 JavaScript高级培训 自定义对象
2010/04/27 Javascript
javascript定时保存表单数据的代码
2011/03/17 Javascript
JQuery操作表格(隔行着色,高亮显示,筛选数据)
2012/02/23 Javascript
异步javascript的原理和实现技巧介绍
2012/11/08 Javascript
简易js代码实现计算器操作
2013/04/15 Javascript
jQuery 遍历- 关于closest() 的方法介绍以及与parents()的方法区别分析
2013/04/26 Javascript
JS调用页面表格导出excel示例代码
2014/03/18 Javascript
jquery 取子节点及当前节点属性值的方法
2014/08/24 Javascript
jQuery事件绑定用法详解
2016/09/08 Javascript
原生JS实现导航下拉菜单效果
2020/11/25 Javascript
Bootstrap框架建立树形菜单(Tree)的实例代码
2017/10/30 Javascript
vue axios 在页面切换时中断请求方法 ajax
2018/03/05 Javascript
vue-router命名路由和编程式路由传参讲解
2019/01/19 Javascript
JS原型和原型链原理与用法实例详解
2020/02/05 Javascript
深入Python函数编程的一些特性
2015/04/13 Python
tf.truncated_normal与tf.random_normal的详细用法
2018/03/05 Python
Python面向对象总结及类与正则表达式详解
2019/04/18 Python
python代码实现逻辑回归logistic原理
2019/08/07 Python
python xlwt如何设置单元格的自定义背景颜色
2019/09/03 Python
python3实现从kafka获取数据,并解析为json格式,写入到mysql中
2019/12/23 Python
PageFactory设计模式基于python实现
2020/04/14 Python
keras的ImageDataGenerator和flow()的用法说明
2020/07/03 Python
python3处理word文档实例分析
2020/12/01 Python
锐步英国官网:Reebok英国
2019/11/29 全球购物
类和结构的区别
2012/08/15 面试题
2014县委书记四风对照检查材料思想汇报
2014/09/21 职场文书
不同意离婚答辩状
2015/05/22 职场文书
公务员的复习计划书,请收下!
2019/07/15 职场文书
redis实现共同好友的思路详解
2021/05/26 Redis
MySQL中order by的使用详情
2021/11/17 MySQL