摘要:另外有哪里寫的不好的地方或看不懂的地方可以給我留言。如果你覺得還有點用,給我這個上點個我會很感激你的哈個人公眾號歡迎關注
靜態服務器
使用node搭建一個可在任何目錄下通過命令啟動的一個簡單http靜態服務器
完整代碼鏈接
安裝:npm install yg-server -g
啟動:yg-server
可通過以上命令安裝,啟動,來看一下最終的效果
TODO創建一個靜態服務器
通過yargs來創建命令行工具
處理緩存
處理壓縮
初始化創建目錄:mkdir static-server
進入到該目錄:cd static-server
初始化項目:npm init
構建文件夾目錄結構:
首先在src目錄下創建一個app.js
引入所有需要的包,非node自帶的需要npm安裝一下
初始化構造函數,options參數由命令行傳入,后續會講到
this.host 主機名
this.port 端口號
this.rootPath 根目錄
this.cors 是否開啟跨域
this.openbrowser 是否自動打開瀏覽器
const http = require("http"); // http模塊 const url = require("url"); // 解析路徑 const path = require("path"); // path模塊 const fs = require("fs"); // 文件處理模塊 const mime = require("mime"); // 解析文件類型 const crypto = require("crypto"); // 加密模塊 const zlib = require("zlib"); // 壓縮 const openbrowser = require("open"); //自動啟動瀏覽器 const handlebars = require("handlebars"); // 模版 const templates = require("./templates"); // 用來渲染的模版文件 class StaticServer { constructor(options) { this.host = options.host; this.port = options.port; this.rootPath = process.cwd(); this.cors = options.cors; this.openbrowser = options.openbrowser; } }處理錯誤響應
在寫具體業務前,先封裝幾個處理響應的函數,分別是錯誤的響應處理,沒有找到資源的響應處理,在后面會調用這么幾個函數來做響應
處理錯誤
返回狀態碼500
返回錯誤信息
responseError(req, res, err) { res.writeHead(500); res.end(`there is something wrong in th server! please try later!`); }
處理資源未找到的響應
返回狀態碼404
返回一個404html
responseNotFound(req, res) { // 這里是用handlerbar處理了一個模版并返回,這個模版只是單純的一個寫著404html const html = handlebars.compile(templates.notFound)(); res.writeHead(404, { "Content-Type": "text/html" }); res.end(html); }處理緩存
在前面的一篇文章里我介紹過node處理緩存的幾種方式,這里為了方便我只使用的協商緩存,通過ETag來做驗證
cacheHandler(req, res, filepath) { return new Promise((resolve, reject) => { const readStream = fs.createReadStream(filepath); const md5 = crypto.createHash("md5"); const ifNoneMatch = req.headers["if-none-match"]; readStream.on("data", data => { md5.update(data); }); readStream.on("end", () => { let etag = md5.digest("hex"); if (ifNoneMatch === etag) { resolve(true); } resolve(etag); }); readStream.on("error", err => { reject(err); }); }); }處理壓縮
通過請求頭accept-encoding來判斷瀏覽器支持的壓縮方式
設置壓縮響應頭,并創建對文件的壓縮方式
compressHandler(req, res) { const acceptEncoding = req.headers["accept-encoding"]; if (/gzip/.test(acceptEncoding)) { res.setHeader("Content-Encoding", "gzip"); return zlib.createGzip(); } else if (/deflate/.test(acceptEncoding)) { res.setHeader("Content-Encoding", "deflate"); return zlib.createDeflate(); } else { return false; } }啟動靜態服務器
添加一個啟動服務器的方法
所有請求都交給this.requestHandler這個函數來處理
監聽端口號
start() { const server = http.createSercer((req, res) => this.requestHandler(req, res)); server.listen(this.port, () => { if (this.openbrowser) { openbrowser(`http://${this.host}:${this.port}`); } console.log(`server started in http://${this.host}:${this.port}`); }); }請求處理
通過url模塊解析請求路徑,獲取請求資源名
獲取請求的文件路徑
通過fs模塊判斷文件是否存在,這里分三種情況
請求路徑是一個文件夾,則調用responseDirectory處理
請求路徑是一個文件,則調用responseFile處理
如果請求的文件不存在,則調用responseNotFound處理
requestHandler(req, res) { // 通過url模塊解析請求路徑,獲取請求文件 const { pathname } = url.parse(req.url); // 獲取請求的文件路徑 const filepath = path.join(this.rootPath, pathname); // 判斷文件是否存在 fs.stat(filepath, (err, stat) => { if (!err) { if (stat.isDirectory()) { this.responseDirectory(req, res, filepath, pathname); } else { this.responseFile(req, res, filepath, stat); } } else { this.responseNotFound(req, res); } }); }處理請求的文件
每次返回文件前,先調用前面我們寫的cacheHandler模塊來處理緩存
如果有緩存則返回304
如果不存在緩存,則設置文件類型,etag,跨域響應頭
調用compressHandler對返回的文件進行壓縮處理
返回資源
responseFile(req, res, filepath, stat) { this.cacheHandler(req, res, filepath).then( data => { if (data === true) { res.writeHead(304); res.end(); } else { res.setHeader("Content-Type", mime.getType(filepath) + ";charset=utf-8"); res.setHeader("Etag", data); this.cors && res.setHeader("Access-Control-Allow-Origin", "*"); const compress = this.compressHandler(req, res); if (compress) { fs.createReadStream(filepath) .pipe(compress) .pipe(res); } else { fs.createReadStream(filepath).pipe(res); } } }, error => { this.responseError(req, res, error); } ); }處理請求的文件夾
如果客戶端請求的是一個文件夾,則返回的應該是該目錄下的所有資源列表,而非一個具體的文件
通過fs.readdir可以獲取到該文件夾下面所有的文件或文件夾
通過map來獲取一個數組對象,是為了把該目錄下的所有資源通過模版去渲染返回給客戶端
responseDirectory(req, res, filepath, pathname) { fs.readdir(filepath, (err, files) => { if (!err) { const fileList = files.map(file => { const isDirectory = fs.statSync(filepath + "/" + file).isDirectory(); return { filename: file, url: path.join(pathname, file), isDirectory }; }); const html = handlebars.compile(templates.fileList)({ title: pathname, fileList }); res.setHeader("Content-Type", "text/html"); res.end(html); } });app.js完整代碼
const http = require("http"); const url = require("url"); const path = require("path"); const fs = require("fs"); const mime = require("mime"); const crypto = require("crypto"); const zlib = require("zlib"); const openbrowser = require("open"); const handlebars = require("handlebars"); const templates = require("./templates"); class StaticServer { constructor(options) { this.host = options.host; this.port = options.port; this.rootPath = process.cwd(); this.cors = options.cors; this.openbrowser = options.openbrowser; } /** * handler request * @param {*} req * @param {*} res */ requestHandler(req, res) { const { pathname } = url.parse(req.url); const filepath = path.join(this.rootPath, pathname); // To check if a file exists fs.stat(filepath, (err, stat) => { if (!err) { if (stat.isDirectory()) { this.responseDirectory(req, res, filepath, pathname); } else { this.responseFile(req, res, filepath, stat); } } else { this.responseNotFound(req, res); } }); } /** * Reads the contents of a directory , response files list to client * @param {*} req * @param {*} res * @param {*} filepath */ responseDirectory(req, res, filepath, pathname) { fs.readdir(filepath, (err, files) => { if (!err) { const fileList = files.map(file => { const isDirectory = fs.statSync(filepath + "/" + file).isDirectory(); return { filename: file, url: path.join(pathname, file), isDirectory }; }); const html = handlebars.compile(templates.fileList)({ title: pathname, fileList }); res.setHeader("Content-Type", "text/html"); res.end(html); } }); } /** * response resource * @param {*} req * @param {*} res * @param {*} filepath */ async responseFile(req, res, filepath, stat) { this.cacheHandler(req, res, filepath).then( data => { if (data === true) { res.writeHead(304); res.end(); } else { res.setHeader("Content-Type", mime.getType(filepath) + ";charset=utf-8"); res.setHeader("Etag", data); this.cors && res.setHeader("Access-Control-Allow-Origin", "*"); const compress = this.compressHandler(req, res); if (compress) { fs.createReadStream(filepath) .pipe(compress) .pipe(res); } else { fs.createReadStream(filepath).pipe(res); } } }, error => { this.responseError(req, res, error); } ); } /** * not found request file * @param {*} req * @param {*} res */ responseNotFound(req, res) { const html = handlebars.compile(templates.notFound)(); res.writeHead(404, { "Content-Type": "text/html" }); res.end(html); } /** * server error * @param {*} req * @param {*} res * @param {*} err */ responseError(req, res, err) { res.writeHead(500); res.end(`there is something wrong in th server! please try later!`); } /** * To check if a file have cache * @param {*} req * @param {*} res * @param {*} filepath */ cacheHandler(req, res, filepath) { return new Promise((resolve, reject) => { const readStream = fs.createReadStream(filepath); const md5 = crypto.createHash("md5"); const ifNoneMatch = req.headers["if-none-match"]; readStream.on("data", data => { md5.update(data); }); readStream.on("end", () => { let etag = md5.digest("hex"); if (ifNoneMatch === etag) { resolve(true); } resolve(etag); }); readStream.on("error", err => { reject(err); }); }); } /** * compress file * @param {*} req * @param {*} res */ compressHandler(req, res) { const acceptEncoding = req.headers["accept-encoding"]; if (/gzip/.test(acceptEncoding)) { res.setHeader("Content-Encoding", "gzip"); return zlib.createGzip(); } else if (/deflate/.test(acceptEncoding)) { res.setHeader("Content-Encoding", "deflate"); return zlib.createDeflate(); } else { return false; } } /** * server start */ start() { const server = http.createServer((req, res) => this.requestHandler(req, res)); server.listen(this.port, () => { if (this.openbrowser) { openbrowser(`http://${this.host}:${this.port}`); } console.log(`server started in http://${this.host}:${this.port}`); }); } } module.exports = StaticServer;創建命令行工具
首先在bin目錄下創建一個config.js
導出一些默認的配置
module.exports = { host: "localhost", port: 3000, cors: true, openbrowser: true, index: "index.html", charset: "utf8" };
然后創建一個static-server.js
這里設置的是一些可執行的命令
并實例化了我們最初在app.js里寫的server類,將options作為參數傳入
最后調用server.start()來啟動我們的服務器
注意 #! /usr/bin/env node這一行不能省略哦
#! /usr/bin/env node const yargs = require("yargs"); const path = require("path"); const config = require("./config"); const StaticServer = require("../src/app"); const pkg = require(path.join(__dirname, "..", "package.json")); const options = yargs .version(pkg.name + "@" + pkg.version) .usage("yg-server [options]") .option("p", { alias: "port", describe: "設置服務器端口號", type: "number", default: config.port }) .option("o", { alias: "openbrowser", describe: "是否打開瀏覽器", type: "boolean", default: config.openbrowser }) .option("n", { alias: "host", describe: "設置主機名", type: "string", default: config.host }) .option("c", { alias: "cors", describe: "是否允許跨域", type: "string", default: config.cors }) .option("v", { alias: "version", type: "string" }) .example("yg-server -p 8000 -o localhost", "在根目錄開啟監聽8000端口的靜態服務器") .help("h").argv; const server = new StaticServer(options); server.start();入口文件
最后回到根目錄下的index.js,將我們的模塊導出,這樣可以在根目錄下通過node index來調試
module.exports = require("./bin/static-server");配置命令
配置命令非常簡單,進入到package.json文件里
加入一句話
"bin": { "yg-server": "bin/static-server.js" },
yg-server是啟動該服務器的命令,可以自己定義
然后執行npm link生成一個符號鏈接文件
這樣你就可以通過命令來執行自己的服務器了
或者將包托管到npm上,然后全局安裝,在任何目錄下你都可以通過你設置的命令來開啟一個靜態服務器,在我們平時總會需要這樣一個靜態服務器
總結寫到這里基本上就寫完了,另外還有幾個模版文件,是用來在客戶端展示的,可以看我的github,我就不貼了,只是一些html而已,你也可以自己設置,這個博客寫多了是在是太卡了,字都打不動了。
另外有哪里寫的不好的地方或看不懂的地方可以給我留言。如果你覺得還有點用,給我github這個上點個star我會很感激你的哈
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101556.html
摘要:提示,如果需要通過實現服務器自動化部署,推薦使用會更方便一些然后在目錄初始化本地倉庫并提交到這時候上已經有我提交的代碼了。再新建一個文件處理部署相關腳本,內容如下將新增的與兩個文件到服務器。 這篇文章將如何搭建hexo,以及如何通過git webhooks實現遠程vps的自動部署 這篇文章適合的條件: 簡單的用于個人博客、公司博客展示,hexo的定位是靜態博客,要實現動態服務器的功能...
摘要:中文資料導航官網七牛鏡像深入淺出系列進階必讀中文文檔被誤解的編寫實戰系列熱門模塊排行榜,方便找出你想要的模塊多線程,真正的非阻塞淺析的類利用編寫異步多線程的實例中與的區別管道拒絕服務漏洞高級編程業界新聞看如何評價他們的首次嘗鮮程序員如何說服 node.js中文資料導航 Node.js HomePage Node官網七牛鏡像 Infoq深入淺出Node.js系列(進階必讀) Nod...
摘要:生成的文件如下由于給文件添加了哈希值,所以每次編譯出來的和都是不一樣的,這會導致有很多冗余文件,所以我們可以每次在生成文件之前,先將原來的文件全部清空。中也有做這個工作的插件,因此我們可以在編譯壓縮添加哈希值之前先將原文將清空。 原文鏈接:http://mrzhang123.github.io/2016/09/07/gulpUse/項目鏈接:https://github.com/MrZ...
閱讀 2739·2023-04-25 14:21
閱讀 1174·2021-11-23 09:51
閱讀 4012·2021-09-22 15:43
閱讀 610·2019-08-30 15:55
閱讀 1559·2019-08-29 11:28
閱讀 2445·2019-08-26 11:44
閱讀 1682·2019-08-23 18:15
閱讀 2880·2019-08-23 16:42