用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 相关文章推荐
javascript中的作用域scope介绍
Dec 28 Javascript
node.js中的querystring.escape方法使用说明
Dec 10 Javascript
jQuery UI插件自定义confirm确认框的方法
Mar 20 Javascript
纯HTML5制作围住神经猫游戏-附源码下载
Aug 23 Javascript
AngularJS身份验证的方法
Feb 17 Javascript
jquery对所有input type=text的控件赋值实现方法
Dec 02 Javascript
JS设置CSS样式的方式汇总
Jan 21 Javascript
简单谈谈关于 npm 5.0 的新坑
Jun 08 Javascript
JavaScript继承与聚合实例详解
Jan 22 Javascript
AntV F2和vue-cli构建移动端可视化视图过程详解
Oct 08 Javascript
浅谈Vue2.4.0 $attrs与inheritAttrs的具体使用
Mar 08 Javascript
VUE UPLOAD 通过ACTION返回上传结果操作
Sep 07 Javascript
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
具有时效性的php加密解密函数代码
2013/06/19 PHP
PHP实现多图片上传类实例
2014/07/26 PHP
ThinkPHP自动转义存储富文本编辑器内容导致读取出错的解决方法
2014/08/08 PHP
php+ajax实现无刷新分页的方法
2014/11/04 PHP
CI框架实现cookie登陆的方法详解
2016/05/18 PHP
js 鼠标点击事件及其它捕获
2009/06/04 Javascript
获取数组中最大最小值方法js代码(自写)
2013/08/12 Javascript
ZeroClipboard插件实现多浏览器复制功能(支持firefox、chrome、ie6)
2014/08/30 Javascript
js实现精确到毫秒的倒计时效果
2016/08/05 Javascript
jQuery使用$获取对象后检查该对象是否存在的实现方法
2016/09/04 Javascript
jstree的简单实例
2016/12/01 Javascript
Angularjs中的ui-bootstrap的使用教程
2017/02/19 Javascript
JavaScript正则获取地址栏中参数的方法
2017/03/02 Javascript
js自定义瀑布流布局插件
2017/05/16 Javascript
AngularJS实现的省市二级联动功能示例【可对选项实现增删】
2017/10/26 Javascript
对vue.js中this.$emit的深入理解
2018/02/23 Javascript
微信小程序使用wxParse解析html的方法教程
2018/07/06 Javascript
p5.js实现简单货车运动动画
2019/10/23 Javascript
rhythmbox中文名乱码问题解决方法
2008/09/06 Python
Python实现简单的可逆加密程序实例
2015/03/05 Python
Python中分数的相关使用教程
2015/03/30 Python
python中的全局变量用法分析
2015/06/09 Python
python3爬取淘宝信息代码分析
2018/02/10 Python
处理Selenium3+python3定位鼠标悬停才显示的元素
2019/07/31 Python
Python tkinter之ComboBox(下拉框)的使用简介
2021/02/05 Python
APM Monaco中国官网:来自摩纳哥珠宝品牌
2017/12/27 全球购物
Theory美国官网:后现代都市风时装品牌
2018/05/09 全球购物
维也纳通行证:Vienna PASS
2019/07/18 全球购物
罗技美国官网:Logitech美国
2020/01/22 全球购物
优秀村官事迹材料
2014/01/10 职场文书
校园之声广播稿
2014/01/31 职场文书
2014基层党员干部学习全国两会心得体会
2014/03/17 职场文书
中学生旷课检讨书模板
2014/10/08 职场文书
六查六看个人剖析材料
2014/10/14 职场文书
组织生活会表态发言材料
2014/10/17 职场文书
治理商业贿赂工作总结
2015/08/10 职场文书