共計 5664 個字符,預計需要花費 15 分鐘才能閱讀完成。
這篇文章的內容主要圍繞靜態文件服務器實現的功能是什么進行講述,文章內容清晰易懂,條理清晰,非常適合新手學習,值得大家去閱讀。感興趣的朋友可以跟隨丸趣 TV 小編一起閱讀吧。希望大家通過這篇文章有所收獲!
首先先構建好項目目錄,項目目錄如下:
project |---bin 命令行實現放置腳本 | |---public 靜態文件服務器默認靜態文件夾 | |---src 實現功能的相關代碼 | | | |__template 模板文件夾 | | | |__app.js 主要功能文件(main 文件) | |__config.js 配置文件 | |---package.josn (初始化)
要啟動一個服務器,我們需要知道這個服務器的啟動時的端口號, 在 config.js 配置一下:
let config = { host: localhost // 提示用 , port:8080 // 服務器啟動時候的默認端口號, path:path.resolve(__dirname, .. , test-dir) // 靜態服務器啟動時默認的工作目錄 }
讀取靜態文件之前首先要先啟動服務器, 之后所有的方法都在 class Server 方法里
//handlebar 編譯模板,得到一個渲染的方法, 然后傳入實際數據數據就可以得到渲染后的 HTML 了 function list() { let tmpl = fs.readFileSync(path.resolve(__dirname, template , list.html), utf8 return handlebars.compile(tmpl);// 進行編譯,*** 渲染 }class Server { constructor(argv) { this.list = list(); this.config = Object.assign({}, this.config, argv); } start() { let server = http.createServer();// 創建服務器 // 當客戶端向服務端發出數據的時候,會出發 request 事件 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)}`); }); } // 發送錯誤信息, sendError(err, req, res) { res.statusCode = 500; res.end(`${err.toString()}`); }} module.exports = Server;
讀取靜態文件
設計思路
首先輸入一個 url 時,可能對應服務器上的一個文件,或者對應一個目錄,
檢查是否文件還是目錄如果文件不存在,返回 404 狀態碼,發送 not found 頁面到客戶端
如果文件存在:打開文件讀取
設置 response header 發送文件到客戶端
如果是目錄就打開目錄列表
async request(req, res) { // 先取到客戶端想要的是文件或文件夾路徑 let { pathname } = url.parse(req.url);// 獲取路徑的文件信息 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) })); //handlebar 編譯模板 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));//inspect 把一個對象轉成字符 this.sendError(e, req, res); } }
緩存支持 / 控制
設計思路
緩存分為強制緩存和對比緩存:
兩類緩存規則可以同時存在,強制緩存優先級高于對比緩存,也就是說,當執行強制緩存的規則時,如果緩存生效,直接使用緩存,不再執行對比緩存規則.
強制緩存如果生效,不需要再和服務器發生交互,而對比緩存不管是否生效,都需要與服務端發生交互
*** 次訪問服務器的時候,服務器返回資源和緩存的標識,客戶端則會把此資源緩存在本地的緩存數據庫中。
第二次客戶端需要此數據的時候,要取得緩存的標識,然后去問一下服務器我的資源是否是 *** 的。如果是 *** 的則直接使用緩存數據,如果不是 *** 的則服務器返回新的資源和緩存規則,客戶端根據緩存規則緩存新的數據。
通過 *** 修改時間來判斷緩存是否可用
Last-Modified:響應時告訴客戶端此資源的 *** 修改時間
If-Modified-Since:當資源過期時(使用 Cache-Control 標識的 max-age),發現資源具有 Last-Modified 聲明,則再次向服務器請求時帶上頭 If-Modified-Since。
服務器收到請求后發現有頭 If-Modified-Since 則與被請求資源的 *** 修改時間進行比對。若 *** 修改時間較新,說明資源又被改動過,則響應 *** 的資源內容并返回 200 狀態碼;
若 *** 修改時間和 If-Modified-Since 一樣,說明資源沒有修改,則響應 304 表示未更新,告知瀏覽器繼續使用所保存的緩存文件。
ETag 是資源標簽。如果資源沒有變化它就不會變。
客戶端想判斷緩存是否可用可以先獲取緩存中文檔的 ETag,然后通過 If-None-Match 發送請求給 Web 服務器詢問此緩存是否可用。
服務器收到請求,將服務器的中此文件的 ETag, 跟請求頭中的 If-None-Match 相比較, 如果值是一樣的, 說明緩存還是 *** 的,Web 服務器將發送 304 Not Modified 響應碼給客戶端表示緩存未修改過,可以使用。
如果不一樣則 Web 服務器將發送該文檔的 *** 版本給瀏覽器客戶端
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 //max-age=30 緩存內容將在 30 秒后失效 res.setHeader( Expires , new Date(Date.now() + 30 * 1000).toGMTString()); let etag = statObj.size; let lastModified = statObj.ctime.toGMTString(); res.setHeader( ETag , etag);// 獲取 ETag res.setHeader(Last-Modified , lastModified);// 服務器文件的 *** 修改時間 // 任何一個對比緩存頭不匹配,則不走緩存 if (isNoneMatch isNoneMatch != etag) {// 緩存過期 return fasle; } if (ifModifiedSince ifModifiedSince != lastModified) {// 緩存過期 return fasle; } // 當請求中存在任何一個對比緩存頭,則返回 304,否則不走緩存 if (isNoneMatch || ifModifiedSince) {// 緩存有效 res.writeHead(304); res.end(); return true; } else { return false; } }
支持 gzip 壓縮
設計思路
瀏覽器都會攜帶自己支持的壓縮類型,最常用的兩種是 gzip 和 deflate。根據請求頭 Accept-Encoding,返回不同的壓縮格式.
getEncoding(req, res) { let acceptEncoding = req.headers[ accept-encoding // 獲取客戶端發送的壓縮請求頭的信息 if (/\bgzip\b/.test(acceptEncoding)) {// 如果是 gzip 的格式 res.setHeader( Content-Encoding , gzip return zlib.createGzip(); } else if (/\bdeflate\b/.test(acceptEncoding)) {// 如果是 deflate 的格式 res.setHeader( Content-Encoding , deflate return zlib.createDeflate(); } else { return null;// 不壓縮 } }
Range 支持,斷點續傳
設計思路
該選項指定下載字節的范圍,常應用于分塊下載文件
服務器告訴客戶端可以使用 range response.setHeader(Accept-Ranges , bytes)
Server 通過請求頭中的 Range:bytes=0-xxx 來判斷是否是做 Range 請求,如果這個值存在而且有效,則只發回請求的那部分文件內容,響應的狀態碼變成 206, 如果無效,則返回 416 狀態碼,表明 Request
getStream(req, res, filepath, statObj) { let start = 0;// 可讀流起始位置 let end = statObj.size - 1;// 可讀流結束位置 let range = req.headers[ range // 獲取客戶端的 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 }); }
發布為可執行命令
首先在 package.json 配置一下 bin : {http-static : bin/www}
#! /usr/bin/env node // 這段代碼一定要寫在開頭,為了兼容各個電腦平臺的差異性 // -d --root 靜態文件目錄 -o --host 主機 -p --port 端口號 let yargs = require(yargs let Server = require( ../src/app.js let argv = yargs.option( d ,{ alias: root , demand: false , type: string , default:process.cwd(), description: 靜態文件跟目錄 }) .option(o ,{ alias: host , demand: localhost , type: string , description: 請配置監聽的主機}) .option(p ,{ alias: root , demand: false , type: number , default:8080, description: 請配置端口號}) .usage(http-static [options] ).example( http-static -d / 8080 -o localhost , 在本機的 9090 端口上監聽客戶端的請求 ).help(h).argv; // argv = {d,root,o,host,p,port}let server = new Server(argv);// 啟動服務 server.start();
這樣命令行當中通過輸入 http-static 來直接啟動靜態文件服務器了,那么命令行調用的功能也就實現了,最后用 npm publish 發布一下,發布到 npm 上面去了,我們就可以通過 npm install - g 來進行全局安裝了。
感謝你的閱讀,相信你對“靜態文件服務器實現的功能是什么”這一問題有一定的了解,快去動手實踐吧,如果想了解更多相關知識點,可以關注丸趣 TV 網站!丸趣 TV 小編會繼續為大家帶來更好的文章!