欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Node 搭建一個(gè)靜態(tài)資源服務(wù)器的實(shí)現(xiàn)

 更新時(shí)間:2019年05月20日 17:00:44   作者:JennyTong  
這篇文章主要介紹了Node 搭建一個(gè)靜態(tài)資源服務(wù)器的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

使用 Node 的內(nèi)置模塊,創(chuàng)建一個(gè)可以訪問目錄的靜態(tài)資源服務(wù)器,支持fs文件讀取,資源壓縮與緩存等。

一、創(chuàng)建 HTTP Server 服務(wù)器

Node 的 http 模塊提供 HTTP 服務(wù)器和客戶端接口,通過 require('http') 使用。

先創(chuàng)建一個(gè)簡單的 http server。配置參數(shù)如下:

// server/config.js
module.exports = {
 root: process.cwd(),
 host: '127.0.0.1',
 port: '8877'
}

process.cwd()方法返回 Node.js 進(jìn)程的當(dāng)前工作目錄,和 Linus 命令 pwd 功能一樣,

Node 服務(wù)器每次收到 HTTP 請(qǐng)求后都會(huì)調(diào)用 http.createServer() 這個(gè)回調(diào)函數(shù),每次收一條請(qǐng)求,都會(huì)先解析請(qǐng)求頭作為新的 request 的一部分,然后用新的 request 和 respond 對(duì)象觸發(fā)回調(diào)函數(shù)。以下創(chuàng)建一個(gè)簡單的 http 服務(wù),先默認(rèn)響應(yīng)的 status 為 200:

// server/http.js
const http = require('http')
const path = require('path')

const config = require('./config')

const server = http.createServer((request, response) => {
 let filePath = path.join(config.root, request.url)
 response.statusCode = 200
 response.setHeader('content-type', 'text/html')
 response.write(`<html><body><h1>Hello World! </h1><p>${filePath}</p></body></html>`)
 response.end()
})

server.listen(config.port, config.host, () => {
 const addr = `http://${config.host}:${config.port}`
 console.info(`server started at ${addr}`)
})

客戶端請(qǐng)求靜態(tài)資源的地址可以通過 request.url 獲得,然后使用 path 模塊拼接資源的路徑。

執(zhí)行 $ node server/http.js 后訪問 http://127.0.0.1 :8877/ 后的任意地址都會(huì)顯示該路徑:

每次修改服務(wù)器響應(yīng)內(nèi)容,都需要重新啟動(dòng)服務(wù)器更新,推薦自動(dòng)監(jiān)視更新自動(dòng)重啟的插件supervisor,使用supervisor啟動(dòng)服務(wù)器。

$ npm install supervisor -D
$ supervisor server/http.js

二、使用 fs 讀取資源文件

我們的目的是搭建一個(gè)靜態(tài)資源服務(wù)器,當(dāng)訪問一個(gè)到資源文件或目錄時(shí),我們希望可以得到它。這時(shí)就需要使用 Node 內(nèi)置的 fs 模塊讀取靜態(tài)資源文件,

使用 fs.stat() 讀取文件狀態(tài)信息,通過回調(diào)中的狀態(tài) stats.isFile() 判斷文件還是目錄,并使用 fs.readdir() 讀取目錄中的文件名

// server/route.js
const fs = require('fs')

module.exports = function (request, response, filePath){
 fs.stat(filePath, (err, stats) => {
  if (err) {
   response.statusCode = 404
   response.setHeader('content-type', 'text/plain')
   response.end(`${filePath} is not a file`)
   return;
  }
  if (stats.isFile()) {
   response.statusCode = 200
   response.setHeader('content-type', 'text/plain')
   fs.createReadStream(filePath).pipe(response)
  } 
  else if (stats.isDirectory()) {
   fs.readdir(filePath, (err, files) => {
    response.statusCode = 200
    response.setHeader('content-type', 'text/plain')
    response.end(files.join(','))
   })
  }
 })
}

其中 fs.createReadStream() 讀取文件流, pipe() 是分段讀取文件到內(nèi)存,優(yōu)化高并發(fā)的情況。

修改之前的 http server ,引入上面新建的 route.js 作為響應(yīng)函數(shù):

// server/http.js
const http = require('http')
const path = require('path')

const config = require('./config')
const route = require('./route')

const server = http.createServer((request, response) => {
 let filePath = path.join(config.root, request.url)
 route(request, response, filePath)
})

server.listen(config.port, config.host, () => {
 const addr = `http://${config.host}:${config.port}`
 console.info(`server started at ${addr}`)
})

再次執(zhí)行 $ node server/http.js 如果是文件夾則顯示目錄:

如果是文件則直接輸出:

成熟的靜態(tài)資源服務(wù)器 anywhere,深入理解 nodejs 作者寫的。

三、util.promisify 優(yōu)化 fs 異步

我們注意到 fs.stat()fs.readdir() 都有 callback 回調(diào)。我們結(jié)合 Node 的 util.promisify() 來鏈?zhǔn)讲僮?,代替地獄回調(diào)。

util.promisify 只是返回一個(gè) Promise 實(shí)例來方便異步操作,并且可以和 async/await 配合使用,修改 route.js 中 fs 操作相關(guān)的代碼:

// server/route.js
const fs = require('fs')
const util = require('util')

const stat = util.promisify(fs.stat)
const readdir = util.promisify(fs.readdir)

module.exports = async function (request, response, filePath) {
 try {
  const stats = await stat(filePath)
  if (stats.isFile()) {
   response.statusCode = 200
   response.setHeader('content-type', 'text/plain')
   fs.createReadStream(filePath).pipe(response)
  }
  else if (stats.isDirectory()) {
   const files = await readdir(filePath)
   response.statusCode = 200
   response.setHeader('content-type', 'text/plain')
   response.end(files.join(','))
  }
 } catch (err) {
  console.error(err)
  response.statusCode = 404
  response.setHeader('content-type', 'text/plain')
  response.end(`${filePath} is not a file`)
 }
}

因?yàn)?fs.stat()fs.readdir() 都可能返回 error,所以使用 try-catch 捕獲。

使用異步時(shí)需注意,異步回調(diào)需要使用 await 返回異步操作,不加 await 返回的是一個(gè) promise,而且 await 必須在async里面使用。

四、添加模版引擎

從上面的例子是手工輸入文件路徑,然后返回資源文件。現(xiàn)在優(yōu)化這個(gè)例子,將文件目錄變成 html 的 a 鏈接,點(diǎn)擊后返回文件資源。

在第一個(gè)例子中使用 response.write() 插入 HTML 標(biāo)簽,這種方式顯然是不友好的。這時(shí)候就使用模版引擎做到拼接 HTML。

常用的模版引擎有很多,ejs、jade、handlebars,這里的使用ejs:

npm i ejs

新建一個(gè)模版 src/template/index.ejs ,和 html 文件很像:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Node Server</title>
</head>
<body>
<% files.forEach(function(name){ %>
 <a href="../<%= dir %>/<%= name %>" rel="external nofollow" > <%= name %></a><br>
<% }) %>
</body>
</html>

再次修改 route.js,添加 ejs 模版并 ejs.render() ,在文件目錄的代碼中傳遞 files、dir 等參數(shù):

// server/route.js

const fs = require('fs')
const util = require('util')
const path = require('path')
const ejs = require('ejs')
const config = require('./config')
// 異步優(yōu)化
const stat = util.promisify(fs.stat)
const readdir = util.promisify(fs.readdir)
// 引入模版
const tplPath = path.join(__dirname,'../src/template/index.ejs')
const sourse = fs.readFileSync(tplPath) // 讀出來的是buffer

module.exports = async function (request, response, filePath) {
 try {
  const stats = await stat(filePath)
  if (stats.isFile()) {
   response.statusCode = 200
   ···
  }
  else if (stats.isDirectory()) {
   const files = await readdir(filePath)
   response.statusCode = 200
   response.setHeader('content-type', 'text/html')
   // response.end(files.join(','))

   const dir = path.relative(config.root, filePath) // 相對(duì)于根目錄
   const data = {
    files,
    dir: dir ? `${dir}` : '' // path.relative可能返回空字符串()
   }

   const template = ejs.render(sourse.toString(),data)
   response.end(template)
  }
 } catch (err) {
  response.statusCode = 404
  ···
 }
}

重啟動(dòng) $ node server/http.js 就可以看到文件目錄的鏈接:

五、匹配文件 MIME 類型

靜態(tài)資源有圖片、css、js、json、html等,

在上面判斷 stats.isFile() 后響應(yīng)頭設(shè)置的 Content-Type 都為 text/plain,但各種文件有不同的 Mime 類型列表。

我們先根據(jù)文件的后綴匹配它的 MIME 類型:

// server/mime.js
const path = require('path')
const mimeTypes = {
 'js': 'application/x-javascript',
 'html': 'text/html',
 'css': 'text/css',
 'txt': "text/plain"
}

module.exports = (filePath) => {
 let ext = path.extname(filePath)
  .split('.').pop().toLowerCase() // 取擴(kuò)展名

 if (!ext) { // 如果沒有擴(kuò)展名,例如是文件
  ext = filePath
 }
 return mimeTypes[ext] || mimeTypes['txt']
}

匹配到文件的 MIME 類型,再使用 response.setHeader('Content-Type', 'XXX') 設(shè)置響應(yīng)頭:

// server/route.js
const mime = require('./mime')
···
  if (stats.isFile()) {
   const mimeType = mime(filePath)
   response.statusCode = 200
   response.setHeader('Content-Type', mimeType)
   fs.createReadStream(filePath).pipe(response)
  }

運(yùn)行 server 服務(wù)器訪問一個(gè)文件,可以看到 Content-Type 修改了:

六、文件傳輸壓縮

注意到 request header 中有 Accept—Encoding:gzip,deflate,告訴服務(wù)器客戶端所支持的壓縮方式,響應(yīng)時(shí) response header 中使用 content-Encoding 標(biāo)志文件的壓縮方式。

node 內(nèi)置 zlib 模塊支持文件壓縮。在前面文件讀取使用的是 fs.createReadStream() ,所以壓縮是對(duì) ReadStream 文件流。示例 gzip,deflate 方式的壓縮:

最常用文件壓縮,gzip等,使用,對(duì)于文件是用ReadStream文件流進(jìn)行讀取的,所以對(duì)ReadStream進(jìn)行壓縮:

// server/compress.js
const zlib = require('zlib')

module.exports = (readStream, request, response) => {
 const acceptEncoding = request.headers['accept-encoding']
 
 if (!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) {
  return readStream
 }
 else if (acceptEncoding.match(/\bgzip\b/)) {
  response.setHeader("Content-Encoding", 'gzip')
  return readStream.pipe(zlib.createGzip())
 }
 else if (acceptEncoding.match(/\bdeflate\b/)) {
  response.setHeader("Content-Encoding", 'deflate')
  return readStream.pipe(zlib.createDeflate())
 }
}

修改 route.js 文件讀取的代碼:

// server/route.js
const compress = require('./compress')
···
 if (stats.isFile()) {
   const mimeType = mime(filePath)
   response.statusCode = 200
   response.setHeader('Content-Type', mimeType)
   
   // fs.createReadStream(filePath).pipe(response)
+   let readStream = fs.createReadStream(filePath)
+   if(filePath.match(config.compress)) { // 正則匹配:/\.(html|js|css|md)/
    readStream = compress(readStream,request, response)
   }
   readStream.pipe(response)
  }

運(yùn)行 server 可以看到不僅 response header 增加壓縮標(biāo)志,而且 3K 大小的資源壓縮到了 1K,效果明顯:

七、資源緩存

以上的 Node 服務(wù)都是瀏覽器首次請(qǐng)求或無緩存狀態(tài)下的,那如果瀏覽器/客戶端請(qǐng)求過資源,一個(gè)重要的前端優(yōu)化點(diǎn)就是緩存資源在客戶端。 緩存有強(qiáng)緩存和協(xié)商緩存

強(qiáng)緩存在 Request Header 中的字段是 Expires 和 Cache-Control;如果在有效期內(nèi)則直接加載緩存資源,狀態(tài)碼直接是顯示 200。

協(xié)商緩存在 Request Header 中的字段是:

  • If-Modified-Since(對(duì)應(yīng)值為上次 Respond Header 中的 Last-Modified)
  • If-None—Match(對(duì)應(yīng)值為上次 Respond Header 中的 Etag)

如果協(xié)商成功則返回 304 狀態(tài)碼,更新過期時(shí)間并加載瀏覽器本地資源,否則返回服務(wù)器端資源文件。

首先配置默認(rèn)的 cache 字段:

// server/config.js
module.exports = {
 root: process.cwd(),
 host: '127.0.0.1',
 port: '8877',
 compress: /\.(html|js|css|md)/,
 cache: {
  maxAge: 2,
  expires: true,
  cacheControl: true,
  lastModified: true,
  etag: true
 }
}

新建 server/cache.js,設(shè)置響應(yīng)頭:

const config = require('./config')
function refreshRes (stats, response) {
 const {maxAge, expires, cacheControl, lastModified, etag} = config.cache;

 if (expires) {
  response.setHeader('Expires', (new Date(Date.now() + maxAge * 1000)).toUTCString());
 }
 if (cacheControl) {
  response.setHeader('Cache-Control', `public, max-age=${maxAge}`);
 }
 if (lastModified) {
  response.setHeader('Last-Modified', stats.mtime.toUTCString());
 }
 if (etag) {
  response.setHeader('ETag', `${stats.size}-${stats.mtime.toUTCString()}`); // mtime 需要轉(zhuǎn)成字符串,否則在 windows 環(huán)境下會(huì)報(bào)錯(cuò)
 }
}

module.exports = function isFresh (stats, request, response) {
 refreshRes(stats, response);

 const lastModified = request.headers['if-modified-since'];
 const etag = request.headers['if-none-match'];

 if (!lastModified && !etag) {
  return false;
 }
 if (lastModified && lastModified !== response.getHeader('Last-Modified')) {
  return false;
 }
 if (etag && etag !== response.getHeader('ETag')) {
  return false;
 }
 return true;
};

最后修改 route.js 中的

// server/route.js
+ const isCache = require('./cache')

  if (stats.isFile()) {
   const mimeType = mime(filePath)
   response.setHeader('Content-Type', mimeType)

+   if (isCache(stats, request, response)) {
    response.statusCode = 304;
    response.end();
    return;
   }
   
   response.statusCode = 200
   // fs.createReadStream(filePath).pipe(response)
   let readStream = fs.createReadStream(filePath)
   if(filePath.match(config.compress)) {
    readStream = compress(readStream,request, response)
   }
   readStream.pipe(response)
  }

重啟 node server 訪問某個(gè)文件,在第一次請(qǐng)求成功時(shí) Respond Header 返回緩存時(shí)間:

一段時(shí)間后再次請(qǐng)求該資源文件,Request Header 發(fā)送協(xié)商請(qǐng)求字段:

以上就是一個(gè)簡單的 Node 靜態(tài)資源服務(wù)器。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Node.js搭建小程序后臺(tái)服務(wù)

    Node.js搭建小程序后臺(tái)服務(wù)

    最近在做微信的應(yīng)用號(hào)小程序開發(fā),小程序的后臺(tái)數(shù)據(jù)接口需要https安全請(qǐng)求,所以需要我的nodejs服務(wù)器能夠提供https的支持,現(xiàn)在就將整個(gè)https服務(wù)器的搭建過程說一下
    2018-01-01
  • npm 更改默認(rèn)全局路徑以及國內(nèi)鏡像的方法

    npm 更改默認(rèn)全局路徑以及國內(nèi)鏡像的方法

    今天小編就為大家分享一篇npm 更改默認(rèn)全局路徑以及國內(nèi)鏡像的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • Node異步和事件循環(huán)的深入講解

    Node異步和事件循環(huán)的深入講解

    異步對(duì)于前端來說是老生常談的話題,同樣學(xué)習(xí)node也離不開異步IO與事件循環(huán),下面這篇文章主要給大家介紹了關(guān)于Node異步和事件循環(huán)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • 教你如何在Node.js中使用jQuery

    教你如何在Node.js中使用jQuery

    本文給大家分享的是如何在Node.js中使用jQuery的方法,包含步驟以及出錯(cuò)的處理,非常的詳細(xì),有需要的小伙伴可以參考下
    2016-08-08
  • Node.js Express 框架 POST方法詳解

    Node.js Express 框架 POST方法詳解

    這篇文章主要介紹了Node.js Express 框架 POST方法詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-01-01
  • node.js安裝及HbuilderX配置詳解

    node.js安裝及HbuilderX配置詳解

    這篇文章主要介紹了node.js安裝及HbuilderX配置的相關(guān)資料,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • Node.js與Sails ~項(xiàng)目結(jié)構(gòu)與Mvc實(shí)現(xiàn)及日志機(jī)制

    Node.js與Sails ~項(xiàng)目結(jié)構(gòu)與Mvc實(shí)現(xiàn)及日志機(jī)制

    Sails是一個(gè)Node.js的中間架構(gòu),很方便的幫助我們搭建web應(yīng)用程序。還有node.js與Sails日志機(jī)制在本文中也講到了,需要的朋友可以一起學(xué)習(xí)下
    2015-10-10
  • Nodejs實(shí)現(xiàn)批量下載妹紙圖

    Nodejs實(shí)現(xiàn)批量下載妹紙圖

    這篇文章主要介紹了使用Nodejs實(shí)現(xiàn)批量下載妹紙圖的方法和詳細(xì)代碼,十分的實(shí)用,喜歡妹紙的小伙伴們可以參考下。
    2015-05-05
  • Node.js命令行/批處理中如何更改Linux用戶密碼淺析

    Node.js命令行/批處理中如何更改Linux用戶密碼淺析

    這篇文章主要給大家介紹了關(guān)于Node.js命令行/批處理中如何更改Linux用戶密碼的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • 如何在NestJS中添加對(duì)Stripe的WebHook驗(yàn)證詳解

    如何在NestJS中添加對(duì)Stripe的WebHook驗(yàn)證詳解

    這篇文章主要為大家介紹了如何在NestJS中添加對(duì)Stripe的WebHook驗(yàn)證詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08

最新評(píng)論