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 相关文章推荐
关于innerHTML后丢失动态绑定的EVENT问题解决方法
May 19 Javascript
判断客户浏览器是否支持cookie的示例代码
Dec 23 Javascript
js获取checkbox复选框选中的选项实例
Aug 24 Javascript
JS实现横向与竖向两个选项卡Tab联动的方法
Sep 27 Javascript
JS+CSS实现DIV层的展开、收缩效果
Jan 28 Javascript
通过示例彻底搞懂js闭包
Aug 10 Javascript
vuejs2.0运用原生js实现简单拖拽元素功能
Aug 21 Javascript
vue项目中运用webpack动态配置打包多种环境域名的方法
Jun 24 Javascript
通过循环优化 JavaScript 程序
Jun 24 Javascript
Moment.js实现多个同时倒计时
Aug 26 Javascript
JS控制GIF图片的停止与显示
Oct 24 Javascript
node.js中 mysql 增删改查操作及async,await处理实例分析
Feb 11 Javascript
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
php生成EXCEL的东东
2006/10/09 PHP
给初学者的30条PHP最佳实践(荒野无灯)
2011/08/02 PHP
PHP-CGI进程CPU 100% 与 file_get_contents 函数的关系分析
2011/08/15 PHP
使用openssl实现rsa非对称加密算法示例
2014/01/24 PHP
php使用google地图应用实例
2014/12/31 PHP
php在apache环境下实现gzip配置方法
2015/04/02 PHP
深入解析PHP底层机制及相关原理
2020/12/11 PHP
图片连续滚动代码[兼容IE/firefox]
2009/06/11 Javascript
jQuery 数据缓存data(name, value)详解及实现
2010/01/04 Javascript
Javascript遍历Html Table示例(包括内容和属性值)
2014/07/08 Javascript
JavaScript检测字符串中是否含有html标签实现方法
2015/07/01 Javascript
javascript判断网页是关闭还是刷新
2015/09/12 Javascript
jquery ztree实现树的搜索功能
2016/02/25 Javascript
vue 组件销毁并重置的实现
2020/01/13 Javascript
[01:35]辉夜杯战队访谈宣传片—LGD
2015/12/25 DOTA
Python矩阵常见运算操作实例总结
2017/09/29 Python
Python基于贪心算法解决背包问题示例
2017/11/27 Python
Python利用递归实现文件的复制方法
2018/10/27 Python
PyQt4编程之让状态栏显示信息的方法
2019/06/18 Python
利用pandas合并多个excel的方法示例
2019/10/10 Python
PyTorch 随机数生成占用 CPU 过高的解决方法
2020/01/13 Python
详解CSS3中的box-sizing(content-box与border-box)
2019/04/19 HTML / CSS
html5小技巧之通过document.head获取head元素
2014/06/04 HTML / CSS
安纳塔拉酒店度假村及水疗官方网站:Anantara Hotel
2016/08/25 全球购物
全球最大化妆品零售网站:SkinStore
2020/10/24 全球购物
英国排名第一的冲浪店:Ann’s Cottage
2020/06/21 全球购物
Java方面的关于数组和继承的笔面试题
2015/09/18 面试题
农业大学毕业生的个人自我评价
2013/10/11 职场文书
大专自我鉴定范文
2013/10/23 职场文书
高三地理教学反思
2014/01/11 职场文书
超市开学活动方案
2014/03/01 职场文书
公司感恩节活动策划书
2014/10/11 职场文书
党的群众路线教育实践活动学习笔记
2014/11/05 职场文书
初中生散播谣言检讨书
2014/11/17 职场文书
小组组名及励志口号
2015/12/24 职场文书
MySQL 外键约束和表关系相关总结
2021/06/20 MySQL