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 相关文章推荐
javascript 极速 隐藏/显示万行表格列只需 60毫秒
Mar 28 Javascript
用jquery实现学校的校历(asp.net+jquery ui 1.72)
Jan 01 Javascript
Javascript 面向对象(二)封装代码
May 23 Javascript
在页面中js获取光标/鼠标的坐标及光标的像素坐标
Nov 11 Javascript
JS中attr和prop属性的区别以及优先选择示例介绍
Jun 30 Javascript
express的中间件bodyParser详解
Dec 04 Javascript
谈谈impress.js初步理解
Sep 09 Javascript
jQuery EasyUI框架中的Datagrid数据表格组件结构详解
Jun 09 Javascript
jquery的父、子、兄弟节点查找,节点的子节点循环方法
Dec 07 Javascript
Vue2单一事件管理组件通信
May 09 Javascript
vue v-for循环重复数据无法添加问题解决方法【加track-by='索引'】
Mar 15 Javascript
SpringBoot+Vue 前后端合并部署的配置方法
Dec 30 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
删除无限级目录与文件代码共享
2006/07/12 PHP
php+dbfile开发小型留言本
2006/10/09 PHP
PHP游戏编程25个脚本代码
2011/02/08 PHP
PHP学习笔记(二) 了解PHP的基本语法以及目录结构
2014/08/04 PHP
WordPress中缩略图的使用以及相关技巧
2015/11/24 PHP
jQuery EasyUI API 中文文档 - ComboBox组合框
2011/10/07 Javascript
js 触发select onchange事件代码
2014/03/20 Javascript
JS实现控制表格行内容垂直对齐的方法
2015/03/30 Javascript
jquery中ajax跨域方法实例分析
2015/12/18 Javascript
javascript的replace方法结合正则使用实例总结
2016/06/16 Javascript
js removeChild 方法深入理解
2016/08/16 Javascript
jquery 中toggle的2种用法详解(推荐)
2016/09/02 Javascript
jQuery使用getJSON方法获取json数据完整示例
2016/09/13 Javascript
JS异步文件分片断点上传的实现思路
2016/12/25 Javascript
JS实现匀加速与匀减速运动的方法示例
2017/09/04 Javascript
switchery按钮的使用方法
2017/12/18 Javascript
解决vue 绑定对象内点击事件失效问题
2018/09/05 Javascript
[01:21]辉夜杯战队访谈宣传片—CDEC
2015/12/25 DOTA
Python选择排序、冒泡排序、合并排序代码实例
2015/04/10 Python
Linux下使用python自动修改本机网关代码分享
2015/05/21 Python
python 性能优化方法小结
2017/03/31 Python
Python SQLite3数据库日期与时间常见函数用法分析
2017/08/14 Python
Python基于回溯法子集树模板解决0-1背包问题实例
2017/09/02 Python
Python3处理HTTP请求的实例
2018/05/10 Python
对numpy数据写入文件的方法讲解
2018/07/09 Python
解决pyinstaller打包pyqt5的问题
2019/01/08 Python
python绘制漏斗图步骤详解
2019/03/04 Python
python在openstreetmap地图上绘制路线图的实现
2019/07/11 Python
10分钟教你用python动画演示深度优先算法搜寻逃出迷宫的路径
2019/08/12 Python
python获取时间戳的实现示例(10位和13位)
2020/09/23 Python
解决python3.6用cx_Oracle库连接Oracle的问题
2020/12/07 Python
质量月口号
2014/06/20 职场文书
创先争优公开承诺书
2014/08/30 职场文书
2015年宣传工作总结
2015/04/08 职场文书
军训心得体会范文(2016最新篇)
2016/01/11 职场文书
《暗黑破坏神2:重制版》本周进行第一轮A测 目前可官网进行申请报名
2021/04/07 其他游戏