亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

Node 搭建一個靜態資源服務器的實現

2024-05-06 16:51:08
字體:
來源:轉載
供稿:網友

使用 Node 的內置模塊,創建一個可以訪問目錄的靜態資源服務器,支持fs文件讀取,資源壓縮與緩存等。

一、創建 HTTP Server 服務器

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

先創建一個簡單的 http server。配置參數如下:

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

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

Node 服務器每次收到 HTTP 請求后都會調用 http.createServer() 這個回調函數,每次收一條請求,都會先解析請求頭作為新的 request 的一部分,然后用新的 request 和 respond 對象觸發回調函數。以下創建一個簡單的 http 服務,先默認響應的 status 為 200:

// server/http.jsconst 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}`)})

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

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

Node,靜態資源服務器

每次修改服務器響應內容,都需要重新啟動服務器更新,推薦自動監視更新自動重啟的插件supervisor,使用supervisor啟動服務器。

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

二、使用 fs 讀取資源文件

我們的目的是搭建一個靜態資源服務器,當訪問一個到資源文件或目錄時,我們希望可以得到它。這時就需要使用 Node 內置的 fs 模塊讀取靜態資源文件,

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

// server/route.jsconst 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() 是分段讀取文件到內存,優化高并發的情況。

修改之前的 http server ,引入上面新建的 route.js 作為響應函數:

// server/http.jsconst 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}`)})

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

Node,靜態資源服務器

如果是文件則直接輸出:

Node,靜態資源服務器

成熟的靜態資源服務器 anywhere,深入理解 nodejs 作者寫的。

三、util.promisify 優化 fs 異步

我們注意到 fs.stat() 和 fs.readdir() 都有 callback 回調。我們結合 Node 的 util.promisify() 來鏈式操作,代替地獄回調。

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

// server/route.jsconst 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`) }}

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

使用異步時需注意,異步回調需要使用 await 返回異步操作,不加 await 返回的是一個 promise,而且 await 必須在async里面使用。

四、添加模版引擎

從上面的例子是手工輸入文件路徑,然后返回資源文件。現在優化這個例子,將文件目錄變成 html 的 a 鏈接,點擊后返回文件資源。

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

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

npm i ejs

新建一個模版 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 等參數:

// server/route.jsconst fs = require('fs')const util = require('util')const path = require('path')const ejs = require('ejs')const config = require('./config')// 異步優化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) // 讀出來的是buffermodule.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) // 相對于根目錄   const data = {    files,    dir: dir ? `${dir}` : '' // path.relative可能返回空字符串()   }   const template = ejs.render(sourse.toString(),data)   response.end(template)  } } catch (err) {  response.statusCode = 404  ··· }}

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

Node,靜態資源服務器

五、匹配文件 MIME 類型

靜態資源有圖片、css、js、json、html等,

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

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

// server/mime.jsconst 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() // 取擴展名 if (!ext) { // 如果沒有擴展名,例如是文件  ext = filePath } return mimeTypes[ext] || mimeTypes['txt']}

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

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

運行 server 服務器訪問一個文件,可以看到 Content-Type 修改了:

Node,靜態資源服務器

六、文件傳輸壓縮

注意到 request header 中有 Accept—Encoding:gzip,deflate,告訴服務器客戶端所支持的壓縮方式,響應時 response header 中使用 content-Encoding 標志文件的壓縮方式。

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

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

// server/compress.jsconst 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.jsconst 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)  }

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

Node,靜態資源服務器

七、資源緩存

以上的 Node 服務都是瀏覽器首次請求或無緩存狀態下的,那如果瀏覽器/客戶端請求過資源,一個重要的前端優化點就是緩存資源在客戶端。 緩存有強緩存和協商緩存 :

強緩存在 Request Header 中的字段是 Expires 和 Cache-Control;如果在有效期內則直接加載緩存資源,狀態碼直接是顯示 200。

協商緩存在 Request Header 中的字段是:

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

如果協商成功則返回 304 狀態碼,更新過期時間并加載瀏覽器本地資源,否則返回服務器端資源文件。

首先配置默認的 cache 字段:

// server/config.jsmodule.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,設置響應頭:

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 需要轉成字符串,否則在 windows 環境下會報錯 }}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 訪問某個文件,在第一次請求成功時 Respond Header 返回緩存時間:

Node,靜態資源服務器

一段時間后再次請求該資源文件,Request Header 發送協商請求字段:

Node,靜態資源服務器

以上就是一個簡單的 Node 靜態資源服務器。希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美最猛性xxxxx免费| 亚洲xxxx做受欧美| 91tv亚洲精品香蕉国产一区7ujn| 日韩欧美精品中文字幕| 色爱av美腿丝袜综合粉嫩av| 日韩成人激情在线| 91久久久久久久久久| 国产欧美一区二区三区四区| www.亚洲一二| 欧美成人午夜激情| 美女啪啪无遮挡免费久久网站| 国产成人精品久久二区二区| 九九热精品视频| 久久久国产视频91| 成人精品久久久| 久久色免费在线视频| 欧美激情精品久久久久| 日韩av电影在线网| 欧美极品少妇xxxxⅹ免费视频| 欧美性极品少妇精品网站| 亚洲精品成人免费| 粉嫩av一区二区三区免费野| 欧美成人在线网站| 97av在线视频免费播放| 精品亚洲一区二区三区在线观看| 日本久久久久久久久| 性欧美xxxx交| 日韩欧美亚洲综合| 欧美极品在线视频| 91亚洲永久免费精品| 日韩av电影手机在线观看| 欧美日韩高清区| 国产精品88a∨| 国产午夜精品美女视频明星a级| 一夜七次郎国产精品亚洲| 92看片淫黄大片看国产片| 国内精品久久久久久中文字幕| 欧美一乱一性一交一视频| 日韩欧美极品在线观看| 中文字幕av一区二区三区谷原希美| 中文字幕精品影院| 国产精品电影久久久久电影网| 色噜噜国产精品视频一区二区| 成人有码在线播放| 性欧美视频videos6一9| 中文字幕亚洲一区| 亚洲精品欧美日韩| 在线播放精品一区二区三区| 亚洲free嫩bbb| 国产精品日韩av| 国产亚洲精品成人av久久ww| 精品国产拍在线观看| 欧美电影在线播放| 亚洲欧美国产va在线影院| 国产a∨精品一区二区三区不卡| 欧美亚洲另类视频| 国产精品99久久久久久www| 亚洲精品网址在线观看| 国自在线精品视频| 亚洲欧洲视频在线| 国产成人精品日本亚洲| 欧美丰满片xxx777| www.日韩.com| 成人激情视频免费在线| 亚洲自拍偷拍在线| 国产亚洲美女久久| 成人激情黄色网| 国产精品久久不能| 久久福利视频导航| 国产精品视频午夜| 欧美第一黄网免费网站| 亚洲一区二区中文字幕| www欧美日韩| 另类美女黄大片| 奇米成人av国产一区二区三区| 51色欧美片视频在线观看| 性色av一区二区三区| 亚洲精品98久久久久久中文字幕| 国产精品91视频| 国产欧美日韩中文| 欧美成aaa人片免费看| 亚洲有声小说3d| 精品国产一区二区三区四区在线观看| 在线电影欧美日韩一区二区私密| 亚洲国产成人精品一区二区| 国模吧一区二区三区| 91久久夜色精品国产网站| 在线a欧美视频| 国产欧美欧洲在线观看| 欧美中文字幕在线| 最新69国产成人精品视频免费| 成人网在线免费看| 国产精品视频男人的天堂| 在线观看成人黄色| 日韩中文娱乐网| 日韩国产欧美精品在线| 亚洲国产精品va在线看黑人| 久久理论片午夜琪琪电影网| 91久久精品在线| 久久久久久久久久国产精品| 国产精品久久久久久久7电影| 久久国产精品电影| 18一19gay欧美视频网站| 色综合天天综合网国产成人网| 亚洲自拍偷拍一区| xvideos国产精品| www.日韩不卡电影av| 国产精品爽爽爽爽爽爽在线观看| 久久国产精品久久国产精品| 亚洲黄页网在线观看| 久久久久久久久国产| 久久影院资源站| 久久久999精品免费| 97久久超碰福利国产精品…| 亚洲色图第三页| 国产一区二区三区四区福利| 国产精品偷伦免费视频观看的| 国产日韩在线免费| 日本成熟性欧美| 欧美极品少妇xxxxx| 国产小视频91| 欧美最猛性xxxxx亚洲精品| 8090成年在线看片午夜| 国产精品久久国产精品99gif| 日韩免费在线免费观看| 欧美性生交xxxxx久久久| 久久天天躁狠狠躁老女人| www.午夜精品| 精品久久久国产精品999| 欧美在线激情网| 久久精品这里热有精品| 欧美大成色www永久网站婷| 欧美日本啪啪无遮挡网站| 91九色视频导航| 一区二区三区视频观看| 日韩电影中文字幕在线观看| 欧美国产日韩中文字幕在线| 亚洲欧美另类人妖| 亚洲精品国产成人| 成人444kkkk在线观看| 亚洲乱亚洲乱妇无码| 亚洲国产精品yw在线观看| 日本一区二三区好的精华液| 66m—66摸成人免费视频| 日韩在线中文字| 国产精品主播视频| 亚洲天堂影视av| 国产精品爽爽ⅴa在线观看| 成人在线观看视频网站| 日韩av在线导航| 久久久久久久av| 日韩精品福利网站| 亚洲人在线观看| 久久夜色撩人精品| 日本在线精品视频| 欧美老少做受xxxx高潮| 色综合色综合久久综合频道88| 国产精品91久久久| 国产在线视频一区| 日韩在线小视频| 日本精品性网站在线观看| 精品女同一区二区三区在线播放| 日韩中文字幕视频在线| 欧美日韩第一视频|