用Node提供靜態文件服務

前言

對於一個web應用,提供靜態文件(CSS、JavaScript、圖片)服務經常是必須的。本文將介紹如何作一個本身的靜態文件服務器。html

建立一個靜態文件服務器

每一個靜態文件服務器都有個根目錄,也就是提供文件服務的基礎目錄。因此咱們要在即將建立的服務器上定義一個root變量,它將做爲咱們這個靜態文件服務器的根目錄:web

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()。用這個方法能夠極大簡化服務器的代碼。 優化後代碼以下:ui

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

server.listen(3000)

這種寫法,是否是更簡單,更清晰了呢?url

理解流和管道

流是Node中很重要的一個概念,你能夠把Node中的管道想象成水管,若是你想讓某個源頭(好比熱水器)流出來的水流到一個目的地(好比廚房的水龍頭),能夠在中間加一個管道把它們連起來,這樣水就會順着管道從源頭流到目的地。
Node中的管道也是這樣,但其中流動的不是水,而是來自源頭(即ReadableStream)的數據,管道可讓它們「流動」到某個目的地(即WritableStream)。你能夠用pipe方法把管道連起來:spa

ReadableStream.pipe(WritableStream)

讀取一個文件(ReadableStream)並把其中的內容寫到另外一個文件中(WritableStream)用的就是管道:code

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

全部ReadableStream都能接入任何一個WritableStream。好比HTTP請求(req)對
象就是ReadableStream,你可讓其中的內容流動到文件中:server

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

運行

如今咱們來運行上面的代碼,咱們在根目錄下放一張圖片,好比peiqi.jpg。
在瀏覽器中輸入http://127.0.0.1:3000/peiqi.jpg,發現可愛的peiqi已經出如今你的面前了。peiqi.jpg被看成響應主體從http服務器送到了客戶端(瀏覽器)。
圖片描述

雖然已經品嚐到了成功的滋味,但這個靜態文件服務器還不夠完整,由於它很容易出錯。想象一下,若是用戶不當心輸入了一個並不存在的資源,好比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處理用戶上傳的文件並存放到服務器中。

相關文章
相關標籤/搜索