用Node提供静态文件服务的方法


Posted in Javascript onJuly 06, 2018

前言

对于一个web应用,提供静态文件(CSS、JavaScript、图片)服务常常是必须的。本文将介绍如何做一个自己的静态文件服务器。

创建一个静态文件服务器

每个静态文件服务器都有个根目录,也就是提供文件服务的基础目录。所以我们要在即将创建的服务器上定义一个root变量,它将作为我们这个静态文件服务器的根目录:

var http = require('http')
var join = require('path').join
var fs = require('fs')

var root = __dirname

__dirname 在Node中是一个神奇的变量,它的值是该文件所在目录的路径。在本例中,服务器会将这个脚本所在的目录作为静态文件的根目录。

有了文件的路径,还需要传输文件的内容。

这可以用fs.ReadStream完成,它是Node中Stream类之一。成功调用 fs.createReadStream() 会返回一个新的 fs.ReadStream 对象。

下面的代码实现了一个简单但功能完备的文件服务器。

var server = http.createServer(function(req, res){
 let path = join(root, req.url)
 let stream = fs.createReadStream(path)
 stream.on('data', function(chunk){
  res.write(chunk)
 })
 stream.on('end', function(){
  res.end()
 })
})

server.listen(3000)

这个文件服务器大体能用,但还有很多细节需要考虑。接下来我们要优化数据的传输,同时也精简一下服务器的代码。

用STREAM.PIPE()优化数据传输

虽然上面的代码看上去还不错,但Node还提供了更高级的实现机制:Stream.pipe()。用这个方法可以极大简化服务器的代码。 优化后代码如下:

var server = http.createServer(function(req, res){
 let path = join(root, req.url)
 let stream = fs.createReadStream(path)
 stream.pipe(res)
})

server.listen(3000)

这种写法,是不是更简单,更清晰了呢?

理解流和管道

流是Node中很重要的一个概念,你可以把Node中的管道想象成水管,如果你想让某个源头(比如热水器)流出来的水流到一个目的地(比如厨房的水龙头),可以在中间加一个管道把它们连起来,这样水就会顺着管道从源头流到目的地。

Node中的管道也是这样,但其中流动的不是水,而是来自源头(即ReadableStream)的数据,管道可以让它们“流动”到某个目的地(即WritableStream)。你可以用pipe方法把管道连起来:

ReadableStream.pipe(WritableStream)

读取一个文件(ReadableStream)并把其中的内容写到另一个文件中(WritableStream)用的就是管道:

let readStream = fs.createReadStream('./original.txt') 
let writeStream = fs.createWriteStream('./copy.txt') 
readStream.pipe(writeStream)

所有ReadableStream都能接入任何一个WritableStream。比如HTTP请求(req)对象就是ReadableStream,你可以让其中的内容流动到文件中:

req.pipe(fs.createWriteStream('./req-body.txt'))

运行

现在我们来运行上面的代码,我们在根目录下放一张图片,比如peiqi.jpg。

在浏览器中输入http://127.0.0.1:3000/peiqi.jpg,发现可爱的peiqi已经出现在你的面前了。peiqi.jpg被当作响应主体从http服务器送到了客户端(浏览器)。

用Node提供静态文件服务的方法

虽然已经品尝到了成功的滋味,但这个静态文件服务器还不够完整,因为它很容易出错。想象一下,如果用户不小心输入了一个并不存在的资源,比如abc.html,服务器就会马上崩掉。所以我们还得给这个文件服务器加上错误处理机制,让它足够健壮。

处理服务器错误

在Node中,所有继承了EventEmitter的类都可能会发出error事件。为了监听错误,在fs.ReadStream上注册一个error事件处理器(比如下面这段代码),返回响应状态码500表明有服务器内部错误:

stream.on('error', function(err){
  res.statusCode = 500
  res.end('服务器内部错误')
 })

用fs.stat()实现错误处理

我们可以用fs.stat()来获取文件的相关信息,如果文件不存在,fs.stat()会在err.code中放入ENOENT作为响应,然后你可以返回错误码404,向客户端表明文件未找到。如果fs.stat()返回了其他错误码,你可以返回通用的错误码500。
重构后的代码如下:

var server = http.createServer(function(req, res){
 let path = join(root, req.url)

 fs.stat(path, function(err, stat) {
  if (err) {
   if ('ENOENT' == err.code) {
    res.statusCode = 404
    res.end('Not Found')
   } else {
    res.statusCode = 500
    res.end('服务器内部错误')
   }
  } else { // 有该文件
   res.setHeader('Content-Length', stat.size)
   var stream = fs.createReadStream(path)
   stream.pipe(res)

   stream.on('error', function(err) { // 如果读取文件出错
    res.statusCode = 500
    res.end('服务器内部错误')
   })
  }
 })
})

server.listen(3000)

注意

本节构建的文件服务器是个简化版。如果你想把它放到生产环境中,应该更全面地检查输入的有效性,以防用户通过目录遍历攻击访问到你本来不想开放给他们的那部分内容。

小结

读到这里,相信聪明的你已经掌握了如何用Node创建一个静态服务器,下一篇文章我会给大家介绍如何用Node处理用户上传的文件并存放到服务器中。

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

Javascript 相关文章推荐
jQuery中offset()方法用法实例
Jan 16 Javascript
node.js回调函数之阻塞调用与非阻塞调用
Nov 13 Javascript
jquery实现简单的表单验证
Nov 17 Javascript
jQuery中on绑定事件后引发的事件冒泡问题如何解决
May 25 Javascript
node.js实现博客小爬虫的实例代码
Oct 08 Javascript
JavaScript中 DOM操作方法小结
Apr 25 Javascript
Swiper实现轮播图效果
Jul 03 Javascript
Angular动态绑定样式及改变UI框架样式的方法小结
Sep 03 Javascript
详解vscode中vue代码颜色插件
Oct 11 Javascript
node.js实现上传文件功能
Jul 15 Javascript
JavaScript位置参数实现原理及过程解析
Sep 14 Javascript
vue整合百度地图显示指定地点信息
Apr 06 Vue.js
vue使用监听实现全选反选功能
Jul 06 #Javascript
vue 实现数字滚动增加效果的实例代码
Jul 06 #Javascript
详解在Vue中使用TypeScript的一些思考(实践)
Jul 06 #Javascript
javascript显示动态时间的方法汇总
Jul 06 #Javascript
Django+Vue跨域环境配置详解
Jul 06 #Javascript
微信小程序画布圆形进度条显示效果
Nov 17 #Javascript
微信小程序实时聊天WebSocket
Jul 05 #Javascript
You might like
雄兵连:天使彦天使彦为爱折翼,彦和炙心同时念动的誓言!
2020/03/02 国漫
层叠菜单的动态生成
2006/10/09 PHP
thinkphp在php7环境下提示Cannot use ‘String’ as class name as it is reserved的解决方法
2016/09/30 PHP
PHP用mysql_insert_id()函数获得刚插入数据或当前发布文章的ID
2016/11/25 PHP
PHP pthreads v3下worker和pool的使用方法示例
2020/02/21 PHP
JavaScript 组件之旅(三):用 Ant 构建组件
2009/10/28 Javascript
EASYUI TREEGRID异步加载数据实现方法
2012/08/22 Javascript
鼠标经过tr时,改变tr当前背景颜色
2014/01/13 Javascript
js拆分字符串并将分割的数据放到数组中的方法
2015/05/06 Javascript
js实现a标签超链接提交form表单的方法
2015/06/24 Javascript
深入分析Javascript事件代理
2016/01/30 Javascript
JavaScript类的写法
2016/09/17 Javascript
JS异步加载的三种实现方式
2017/03/16 Javascript
实现微信小程序的wxml文件和wxss文件在webstrom的支持
2017/06/12 Javascript
JavaScript代码判断输入的字符串是否含有特殊字符和表情代码实例
2017/08/17 Javascript
JavaScript 中的 this 简单规则
2017/09/19 Javascript
浅谈Vue路由快照实现思路及其问题
2018/06/07 Javascript
详解Vue改变数组中对象的属性不重新渲染View的解决方案
2018/09/21 Javascript
python实现迭代法求方程组的根过程解析
2019/11/25 Javascript
[01:20]2018DOTA2亚洲邀请赛总决赛战队LGD晋级之路
2018/04/07 DOTA
[01:33]完美世界DOTA2联赛PWL S3 集锦第二期
2020/12/21 DOTA
Python实现全局变量的两个解决方法
2014/07/03 Python
Python实现将sqlite数据库导出转成Excel(xls)表的方法
2017/07/17 Python
Django中的Signal代码详解
2018/02/05 Python
Python格式化输出%s和%d
2018/05/07 Python
Python中的 ansible 动态Inventory 脚本
2020/01/19 Python
python encrypt 实现AES加密的实例详解
2020/02/20 Python
利用python下载scihub成文献为PDF操作
2020/07/09 Python
详解CSS3实现响应式手风琴效果
2020/06/10 HTML / CSS
英国计算机产品零售商:Novatech(定制个人电脑、笔记本电脑、工作站和服务器)
2018/01/28 全球购物
如何写一份好的自荐信
2014/01/02 职场文书
期末学生评语大全
2014/04/24 职场文书
我读书我快乐演讲稿
2014/05/07 职场文书
乔布斯斯坦福大学演讲稿
2014/05/23 职场文书
2014年学生资助工作总结
2014/12/18 职场文书
普通员工辞职信范文
2015/05/12 职场文书