首頁 web前端 js教程 怎麼操作Node靜態資源伺服器

怎麼操作Node靜態資源伺服器

May 09, 2018 am 11:47 AM
node 資源 靜態

這次帶給大家怎麼操作Node靜態資源伺服器,操作Node靜態資源伺服器的注意事項有哪些,下面就是實戰案例,一起來看一下。

http伺服器是繼承自tcp伺服器http協議是應用層協議,是基於TCP的

#http的原理是對請求和回應進行了包裝,當客戶端連接上來之後先觸發connection事件,然後可以多次發送請求,每次請求都會觸發request事件

let server = http.createServer();
let url = require('url');
server.on('connection', function (socket) {
  console.log('客户端连接 ');
});
server.on('request', function (req, res) {
  let { pathname, query } = url.parse(req.url, true);
  let result = [];
  req.on('data', function (data) {
    result.push(data);
  });
  req.on('end', function () {
    let r = Buffer.concat(result);
    res.end(r);
  })
});
server.on('close', function (req, res) {
  console.log('服务器关闭 ');
});
server.on('error', function (err) {
  console.log('服务器错误 ');
});
server.listen(8080, function () {
  console.log('server started at http://localhost:8080');
});
登入後複製
  1. #req 代表客戶端的連接,server伺服器把客戶端的請求訊息進行解析,然後放在req上面

  2. res 代表回應,如果希望向客戶端回應訊息,需要透過res

  3. req和res都是從socket來的,先監聽socket的data事件,然後等事件發生的時候,進行解析,解析出請頭對象,再創建請求對象,再根據請求對象創建響應對象

  4. req.url 取得請求路徑

  5. req.headers 請求頭物件

接下來我們對一些核心功能進行解說

深刻理解並實現壓縮和解壓縮

為什麼要壓縮?有什麼好處?

可以使用zlib模組進行壓縮及解壓縮處理,壓縮檔案以後可以減少體積,加快傳輸速度和節約頻寬代碼

壓縮和解壓縮物件都是transform轉換流,繼承自duplex雙工流即可讀可寫流

  1. zlib.createGzip:返回Gzip流對象,使用Gzip演算法對資料進行壓縮處理

  2. zlib.createGunzip:傳回Gzip流對象,使用Gzip演算法對壓縮的資料進行解壓縮處理

  3. zlib.createDeflate:傳回Deflate流對象,使用Deflate演算法對資料進行壓縮處理

  4. zlib.createInflate:傳回Deflate流對象,使用Deflate演算法對資料進行解壓縮處理

實現壓縮和解壓縮

因為壓縮我檔案可能很大也可能很小,所以為了提高處理速度,我們用流來實現

let fs = require("fs");
let path = require("path");
let zlib = require("zlib");
function gzip(src) {
 fs
  .createReadStream(src)
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream(src + ".gz"));
}
gzip(path.join(dirname,'msg.txt'));
function gunzip(src) {
 fs
  .createReadStream(src)
  .pipe(zlib.createGunzip())
  .pipe(
   fs.createWriteStream(path.join(dirname, path.basename(src, ".gz")))
  );
}
gunzip(path.join(dirname, "msg.txt.gz"));
登入後複製
  1. # gzip方法用來實作壓縮

  2. gunzip方法用來實作解壓縮

  3. 其中檔案msg.txt是同級目錄

  4. 為什麼需要這麼寫:gzip(path.join(dirname,'msg.txt'));

  5. 因為console.log(process.cwd() );列印出目前工作目錄是根目錄,並不是檔案所在目錄,如果這麼寫gzip('msg.txt');找不到檔案就會報錯

  6. basename 從一個路徑中得到檔名,包含副檔名的,可以傳一個副檔名參數,去掉副檔名

  7. #extname 取得副檔名

  8. 壓縮的格式和解壓縮的格式需要對上,否則會報錯

有些時候我們拿到的字串不是一個流,那怎麼解決呢

let zlib=require('zlib');
let str='hello';
zlib.gzip(str,(err,buffer)=>{
  console.log(buffer.length);
  zlib.unzip(buffer,(err,data)=>{
    console.log(data.toString());
  })
});
登入後複製

有可能壓縮後的內容比原來還大,要是內容太少的話,壓縮也沒什麼意義了

文字壓縮的效果會好一點,因為有規律

在http中應用壓縮和解壓縮

下面實作這樣一個功能,如圖:

#客戶端向伺服器發起請求的時候,會透過accept-encoding(例如:Accept- Encoding:gzip,default)告訴伺服器我支援的解壓縮的格式

  1. #伺服器端需要根據Accept-Encoding顯示的格式進行壓縮,沒有的格式就無法壓縮,因為瀏覽器無法解壓縮

  2. 如果客戶端需要的Accept-Encoding中的格式服務端沒有,也無法實作壓縮

  3. ##
    let http = require("http");
    let path = require("path");
    let url = require("url");
    let zlib = require("zlib");
    let fs = require("fs");
    let { promisify } = require("util");
    let mime = require("mime");
    //把一个异步方法转成一个返回promise的方法
    let stat = promisify(fs.stat);
    http.createServer(request).listen(8080);
    async function request(req, res) {
     let { pathname } = url.parse(req.url); 
     let filepath = path.join(dirname, pathname); 
     // fs.stat(filepath,(err,stat)=>{});现在不这么写了,异步的处理起来比较麻烦
     try {
      let statObj = await stat(filepath);
      res.setHeader("Content-Type", mime.getType(pathname));
      let acceptEncoding = req.headers["accept-encoding"];
      if (acceptEncoding) {
       if (acceptEncoding.match(/\bgzip\b/)) {
        
        res.setHeader("Content-Encoding", "gzip");
        fs
         .createReadStream(filepath)
         .pipe(zlib.createGzip())
         .pipe(res);
       } else if (acceptEncoding.match(/\bdeflate\b/)) {
        res.setHeader("Content-Encoding", "deflate");
        fs
         .createReadStream(filepath)
         .pipe(zlib.createDeflate())
         .pipe(res);
       } else {
        fs.createReadStream(filepath).pipe(res);
       }
      } else {
       fs.createReadStream(filepath).pipe(res);
      }
     } catch (e) {
      res.statusCode = 404;
      res.end("Not Found");
     }
    }
    登入後複製
  1. #mime:透過檔案的名稱、路徑拿到一個檔案的內容類型, 可以依照不同的檔案內容類型傳回不同的Content-Type

  2. acceptEncoding:全部写成小写是为了兼容不同的浏览器,node把所有的请求头全转成了小写

  3. filepath:得到文件的绝对路径

  4. 启动服务后,访问http://localhost:8080/msg.txt 可看到结果

深刻理解并实现缓存

为什么要缓存呢,缓存有什么好处?

  1. 减少了冗余的数据传输,节省了网费。

  2. 减少了服务器的负担, 大大提高了网站的性能

  3. 加快了客户端加载网页的速度

缓存的分类

强制缓存:

强制缓存,在缓存数据未失效的情况下,可以直接使用缓存数据
在没有缓存数据的时候,浏览器向服务器请求数据时,服务器会将数据和缓存规则一并返回,缓存规则信息包含在响应header中

对比缓存:

浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中

再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据

两类缓存的区别和联系

强制缓存如果生效,不需要再和服务器发生交互,而对比缓存不管是否生效,都需要与服务端发生交互
两类缓存规则可以同时存在,强制缓存优先级高于对比缓存,也就是说,当执行强制缓存的规则时,如果缓存生效,直接使用缓存,不再执行对比缓存规则

实现对比缓存

实现对比缓存一般是按照以下步骤:

第一次访问服务器的时候,服务器返回资源和缓存的标识,客户端则会把此资源缓存在本地的缓存数据库中。

第二次客户端需要此数据的时候,要取得缓存的标识,然后去问一下服务器我的资源是否是最新的。

如果是最新的则直接使用缓存数据,如果不是最新的则服务器返回新的资源和缓存规则,客户端根据缓存规则缓存新的数据

实现对比缓存一般有两种方式

通过最后修改时间来判断缓存是否可用

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
// http://localhost:8080/index.html
http.createServer(function (req, res) {
  let { pathname } = url.parse(req.url, true);
  //D:\vipcode\201801\20.cache\index.html
  let filepath = path.join(dirname, pathname);
  fs.stat(filepath, (err, stat) => {
    if (err) {
      return sendError(req, res);
    } else {
      let ifModifiedSince = req.headers['if-modified-since'];
      let LastModified = stat.ctime.toGMTString();
      if (ifModifiedSince == LastModified) {
        res.writeHead(304);
        res.end('');
      } else {
        return send(req, res, filepath, stat);
      }
    }
  });
}).listen(8080);
function sendError(req, res) {
  res.end('Not Found');
}
function send(req, res, filepath, stat) {
  res.setHeader('Content-Type', mime.getType(filepath));
  //发给客户端之后,客户端会把此时间保存起来,下次再获取此资源的时候会把这个时间再发回服务器
  res.setHeader('Last-Modified', stat.ctime.toGMTString());
  fs.createReadStream(filepath).pipe(res);
}
登入後複製

这种方式有很多缺陷

  1. 某些服务器不能精确得到文件的最后修改时间, 这样就无法通过最后修改时间来判断文件是否更新了

  2. 某些文件的修改非常频繁,在秒以下的时间内进行修改.Last-Modified只能精确到秒。

  3. 一些文件的最后修改时间改变了,但是内容并未改变。 我们不希望客户端认为这个文件修改了

  4. 如果同样的一个文件位于多个CDN服务器上的时候内容虽然一样,修改时间不一样

ETag

ETag是根据实体内容生成的一段hash字符串,可以标识资源的状态
资源发生改变时,ETag也随之发生变化。 ETag是Web服务端产生的,然后发给浏览器客户端

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
let crypto = require('crypto');
http.createServer(function (req, res) {
  let { pathname } = url.parse(req.url, true);
  
  let filepath = path.join(dirname, pathname);
  fs.stat(filepath, (err, stat) => {
    if (err) {
      return sendError(req, res);
    } else {
      let ifNoneMatch = req.headers['if-none-match'];
      let out = fs.createReadStream(filepath);
      let md5 = crypto.createHash('md5');
      out.on('data', function (data) {
        md5.update(data);
      });
      out.on('end', function () {
      
        let etag = md5.digest('hex');
        let etag = `${stat.size}`;
        if (ifNoneMatch == etag) {
          res.writeHead(304);
          res.end('');
        } else {
          return send(req, res, filepath, etag);
        }
      });
    }
  });
}).listen(8080);
function sendError(req, res) {
  res.end('Not Found');
}
function send(req, res, filepath, etag) {
  res.setHeader('Content-Type', mime.getType(filepath));
  
  res.setHeader('ETag', etag);
  fs.createReadStream(filepath).pipe(res);
}
登入後複製

客户端想判断缓存是否可用可以先获取缓存中文档的ETag,然后通过If-None-Match发送请求给Web服务器询问此缓存是否可用。

服务器收到请求,将服务器的中此文件的ETag,跟请求头中的If-None-Match相比较,如果值是一样的,说明缓存还是最新的,Web服务器将发送304 Not Modified响应码给客户端表示缓存未修改过,可以使用。

如果不一样则Web服务器将发送该文档的最新版本给浏览器客户端

实现强制缓存

把资源缓存在客户端,如果客户端再次需要此资源的时候,先获取到缓存中的数据,看是否过期,如果过期了。再请求服务器

如果没过期,则根本不需要向服务器确认,直接使用本地缓存即可

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
let crypto = require('crypto');
http.createServer(function (req, res) {
  let { pathname } = url.parse(req.url, true);
  let filepath = path.join(dirname, pathname);
  console.log(filepath);
  fs.stat(filepath, (err, stat) => {
    if (err) {
      return sendError(req, res);
    } else {
      send(req, res, filepath);
    }
  });
}).listen(8080);
function sendError(req, res) {
  res.end('Not Found');
}
function send(req, res, filepath) {
  res.setHeader('Content-Type', mime.getType(filepath));
  res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString());
  res.setHeader('Cache-Control', 'max-age=30');
  fs.createReadStream(filepath).pipe(res);
}
登入後複製

浏览器会将文件缓存到Cache目录,第二次请求时浏览器会先检查Cache目录下是否含有该文件,如果有,并且还没到Expires设置的时间,即文件还没有过期,那么此时浏览器将直接从Cache目录中读取文件,而不再发送请求

Expires是服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据

Cache-Control与Expires的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据,如果同时设置的话,其优先级高于Expires

下面开始写静态服务器

首先创建一个http服务,配置监听端口

 let http = require('http');
 let server = http.createServer();
    server.on('request', this.request.bind(this));
    server.listen(this.config.port, () => {
      let url = `http://${this.config.host}:${this.config.port}`;
      debug(`server started at ${chalk.green(url)}`);
    });
登入後複製

下面写个静态文件服务器

先取到客户端想说的文件或文件夹路径,如果是目录的话,应该显示目录下面的文件列表

 async request(req, res) {
    let { pathname } = url.parse(req.url);
    if (pathname == '/favicon.ico') {
      return this.sendError('not found', req, res);
    }
    let filepath = path.join(this.config.root, pathname);
    try {
      let statObj = await stat(filepath);
      if (statObj.isDirectory()) {
        let files = await readdir(filepath);
        files = files.map(file => ({
          name: file,
          url: path.join(pathname, file)
        }));
        let html = this.list({
          title: pathname,
          files
        });
        res.setHeader('Content-Type', 'text/html');
        res.end(html);
      } else {
        this.sendFile(req, res, filepath, statObj);
      }
    } catch (e) {
      debug(inspect(e));
      this.sendError(e, req, res);
    }
  }
  
  sendFile(req, res, filepath, statObj) {
    if (this.handleCache(req, res, filepath, statObj)) return;
    res.setHeader('Content-Type', mime.getType(filepath) + ';charset=utf-8');
    let encoding = this.getEncoding(req, res);
    let rs = this.getStream(req, res, filepath, statObj);
    if (encoding) {
      rs.pipe(encoding).pipe(res);
    } else {
      rs.pipe(res);
    }
  }
登入後複製

支持断点续传

 getStream(req, res, filepath, statObj) {
    let start = 0;
    let end = statObj.size - 1;
    let range = req.headers['range'];
    if (range) {
      res.setHeader('Accept-Range', 'bytes');
      res.statusCode = 206;
      let result = range.match(/bytes=(\d*)-(\d*)/);
      if (result) {
        start = isNaN(result[1]) ? start : parseInt(result[1]);
        end = isNaN(result[2]) ? end : parseInt(result[2]) - 1;
      }
    }
    return fs.createReadStream(filepath, {
      start, end
    });
  }
登入後複製

支持对比缓存,通过etag的方式

handleCache(req, res, filepath, statObj) {
    let ifModifiedSince = req.headers['if-modified-since'];
    let isNoneMatch = req.headers['is-none-match'];
    res.setHeader('Cache-Control', 'private,max-age=30');
    res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toGMTString());
    let etag = statObj.size;
    let lastModified = statObj.ctime.toGMTString();
    res.setHeader('ETag', etag);
    res.setHeader('Last-Modified', lastModified);
    if (isNoneMatch && isNoneMatch != etag) {
      return fasle;
    }
    if (ifModifiedSince && ifModifiedSince != lastModified) {
      return fasle;
    }
    if (isNoneMatch || ifModifiedSince) {
      res.writeHead(304);
      res.end();
      return true;
    } else {
      return false;
    }
  }
登入後複製

支持文件压缩

  getEncoding(req, res) {
    let acceptEncoding = req.headers['accept-encoding'];
    if (/\bgzip\b/.test(acceptEncoding)) {
      res.setHeader('Content-Encoding', 'gzip');
      return zlib.createGzip();
    } else if (/\bdeflate\b/.test(acceptEncoding)) {
      res.setHeader('Content-Encoding', 'deflate');
      return zlib.createDeflate();
    } else {
      return null;
    }
  }
登入後複製

编译模板,得到一个渲染的方法,然后传入实际数据数据就可以得到渲染后的HTML了

function list() {
  let tmpl = fs.readFileSync(path.resolve(dirname, 'template', 'list.html'), 'utf8');
  return handlebars.compile(tmpl);
}
登入後複製

这样一个简单的静态服务器就完成了,其中包含了静态文件服务,实现缓存,实现断点续传,分块获取,实现压缩的功能

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

在vue-router里query动态传参步骤有哪些

本地开发环境不能用IP访问如何处理

以上是怎麼操作Node靜態資源伺服器的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

nvm 怎麼刪除node nvm 怎麼刪除node Dec 29, 2022 am 10:07 AM

nvm刪除node的方法:1、下載「nvm-setup.zip」並將其安裝在C碟;2、設定環境變量,並透過「nvm -v」指令查看版本號;3、使用「nvm install」指令安裝node;4、透過「nvm uninstall」指令刪除已安裝的node即可。

node專案中如何使用express來處理檔案的上傳 node專案中如何使用express來處理檔案的上傳 Mar 28, 2023 pm 07:28 PM

怎麼處理文件上傳?以下這篇文章為大家介紹一下node專案中如何使用express來處理文件的上傳,希望對大家有幫助!

115網盤怎麼找資源 115網盤怎麼找資源 Feb 23, 2024 pm 05:10 PM

115網盤裡會有很多的資源,那該怎麼找資源呢?使用者可以在軟體裡搜尋所需的資源,然後進入下載介面,然後選擇儲存到網盤就可以了。這篇115網盤找資源方法介紹就能夠告訴大家具體的內容,以下就是詳細的介紹,趕緊來看看吧。 115網盤怎麼找資源答:在軟體裡搜尋內容,然後點選儲存至網盤。具體介紹:1、先在app裡輸入想要的資源。 2、之後點選出現的關鍵字連結。 3、接著進入下載介面。 4.點擊裡面的存到網盤就可以了。

深入淺析Node的進程管理工具'pm2” 深入淺析Node的進程管理工具'pm2” Apr 03, 2023 pm 06:02 PM

這篇文章跟大家分享Node的進程管理工具“pm2”,聊聊為什麼需要pm2、安裝和使用pm2的方法,希望對大家有幫助!

Pi Node教學:什麼是Pi節點?如何安裝和設定Pi Node? Pi Node教學:什麼是Pi節點?如何安裝和設定Pi Node? Mar 05, 2025 pm 05:57 PM

PiNetwork節點詳解及安裝指南本文將詳細介紹PiNetwork生態系統中的關鍵角色——Pi節點,並提供安裝和配置的完整步驟。 Pi節點在PiNetwork區塊鏈測試網推出後,成為眾多先鋒積極參與測試的重要環節,為即將到來的主網發布做準備。如果您還不了解PiNetwork,請參考Pi幣是什麼?上市價格多少? Pi用途、挖礦及安全性分析。什麼是PiNetwork? PiNetwork項目始於2019年,擁有其專屬加密貨幣Pi幣。該項目旨在創建一個人人可參與

npm node gyp失敗怎麼辦 npm node gyp失敗怎麼辦 Dec 29, 2022 pm 02:42 PM

npm node gyp失敗是因為“node-gyp.js”跟“Node.js”版本不匹配,其解決辦法:1、透過“npm cache clean -f”清除node快取;2、透過“npm install -g n”安裝n模組;3、透過「n v12.21.0」指令安裝「node v12.21.0」版本即可。

使用Angular和Node進行基於令牌的身份驗證 使用Angular和Node進行基於令牌的身份驗證 Sep 01, 2023 pm 02:01 PM

身份驗證是任何網路應用程式中最重要的部分之一。本教程討論基於令牌的身份驗證系統以及它們與傳統登入系統的差異。在本教程結束時,您將看到一個用Angular和Node.js編寫的完整工作演示。傳統身份驗證系統在繼續基於令牌的身份驗證系統之前,讓我們先來看看傳統的身份驗證系統。使用者在登入表單中提供使用者名稱和密碼,然後點擊登入。發出請求後,透過查詢資料庫在後端驗證使用者。如果請求有效,則使用從資料庫中獲取的使用者資訊建立會話,然後在回應頭中傳回會話訊息,以便將會話ID儲存在瀏覽器中。提供用於存取應用程式中受

深入解析C語言中static關鍵字的作用與用法 深入解析C語言中static關鍵字的作用與用法 Feb 20, 2024 pm 04:30 PM

深入解析C語言中static關鍵字的功能和用法在C語言中,static是一種非常重要的關鍵字,它可以被用於函數、變數和資料類型的定義。使用static關鍵字可以改變物件的連結屬性、作用域和生命週期,以下就來詳細解析一下static關鍵字在C語言中的作用和用法。 static變數與函數:在函數內部使用static關鍵字定義的變數稱為靜態變量,它具有全域生命週

See all articles