當(dāng)你輸入一個(gè)url時(shí),這個(gè)url可能對(duì)應(yīng)服務(wù)器上的一個(gè)資源(文件)也可能對(duì)應(yīng)一個(gè)目錄。 So服務(wù)器會(huì)對(duì)這個(gè)url進(jìn)行分析,針對(duì)不同的情況做不同的事。 如果這個(gè)url對(duì)應(yīng)的是一個(gè)文件,那么服務(wù)器就會(huì)返回這個(gè)文件。 如果這個(gè)url對(duì)應(yīng)的是一個(gè)文件夾,那么服務(wù)器會(huì)返回這個(gè)文件夾下包含的所有子文件/子文件夾的列表。 以上,就是一個(gè)靜態(tài)服務(wù)器所主要干的事。
成都創(chuàng)新互聯(lián)公司專(zhuān)注于綏陽(yáng)網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供綏陽(yáng)營(yíng)銷(xiāo)型網(wǎng)站建設(shè),綏陽(yáng)網(wǎng)站制作、綏陽(yáng)網(wǎng)頁(yè)設(shè)計(jì)、綏陽(yáng)網(wǎng)站官網(wǎng)定制、小程序設(shè)計(jì)服務(wù),打造綏陽(yáng)網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供綏陽(yáng)網(wǎng)站排名全網(wǎng)營(yíng)銷(xiāo)落地服務(wù)。
但真實(shí)的情況不會(huì)像這么簡(jiǎn)單, 我們所拿到的url可能是錯(cuò)誤的,它所對(duì)應(yīng)的文件或則文件夾或許根本不存在, 又或則有些文件和文件夾是被系統(tǒng)保護(hù)起來(lái)的是隱藏的,我們并不想讓客戶(hù)端知道。 因此,我們就要針對(duì)這些特殊情況進(jìn)行一些不同的返回和提示。
再者,當(dāng)我們真正返回一個(gè)文件前,我們需要和客戶(hù)端進(jìn)行一些協(xié)商。 我們需要知道客戶(hù)端能夠接受的語(yǔ)言類(lèi)型、編碼方式等等以便針對(duì)不同瀏覽器進(jìn)行不同的返回處理。 我們需要告訴客戶(hù)端一些關(guān)于返回文件的額外信息,以便客戶(hù)端能更好的接收數(shù)據(jù): 文件是否需要緩存,該怎樣緩存? 文件是否進(jìn)行了壓縮處理,該以怎樣的方式解壓? 等等...
至此,我們已經(jīng)初步了解了一個(gè)靜態(tài)服務(wù)器所主要做的幾乎所有事情, let's go!
實(shí)現(xiàn)
項(xiàng)目目錄
static-server/ | | - bin/ | | - start # 批處理文件 | | | - src/ | | - App.js # main文件 | | - Config.js # 默認(rèn)配置 | | ·- package.json
配置文件
要啟動(dòng)一個(gè)服務(wù)器,我們需要知道這個(gè)服務(wù)器的啟動(dòng)時(shí)的端口號(hào)和靜態(tài)服務(wù)器的工作目錄
let config = { host:'localhost' //提升用 ,port:8080 //服務(wù)器啟動(dòng)時(shí)候的默認(rèn)端口號(hào) ,path:path.resolve(__dirname,'..','test-dir') //靜態(tài)服務(wù)器啟動(dòng)時(shí)默認(rèn)的工作目錄 }
整體框架
注意
事件函數(shù)中的this默認(rèn)指向綁定的對(duì)象(這里是小server),這里修改成了Server這個(gè)大對(duì)象,以便調(diào)用在回調(diào)函數(shù)中調(diào)用Server下的方法。
class Server(){ constructor(options){ /* === 合并配置參數(shù) === */ this.config = Object.assign({},config,options) } start(){ /* === 啟動(dòng)http服務(wù) === */ let server = http.createServer(); server.on('request',this.request.bind(this)); server.listen(this.config.port,()=>{ let url = `${this.config.host}:${this.config.port}`; console.log(`server started at ${chalk.green(url)}`) }) } async request(req,res){ /* === 處理客戶(hù)端請(qǐng)求,決定響應(yīng)信息 === */ // try //如果是文件夾 -> 顯示子文件、文件夾列表 //如果是文件 -> sendFile() // catch //出錯(cuò) -> sendError() } sendFile(){ //對(duì)要返回的文件進(jìn)行預(yù)處理并發(fā)送文件 } handleCache(){ //獲取和設(shè)置緩存相關(guān)信息 } getEncoding(){ //獲取和設(shè)置編碼相關(guān)信息 } getStream(){ //獲取和設(shè)置分塊傳輸相關(guān)信息 } sendError(){ //錯(cuò)誤提示 } } module.exports = Server;
request請(qǐng)求處理
獲取url的 pathname ,和 服務(wù)器本地的工作根目錄地址 進(jìn)行拼接,返回一個(gè) filename 利用filename和 stat方法 檢測(cè)是文件還是文件夾
是文件夾, 利用 readdir方法 返回該文件夾下的列表,將列表包裝成一個(gè)對(duì)象組成的數(shù)組 然后結(jié)合handlebar將數(shù)組數(shù)據(jù)編譯到模板中,最后返回這個(gè)模板給客戶(hù)端
是文件, 將req、res、statObj、filepath傳遞給 sendFile ,接下來(lái)交由sendFile處理
async request(req,res){ let pathname = url.parse(req.url); if(pathname == '/favicon.ico') return; let filepath = path.join(this.config.root,pathname); try{ let statObj = await stat(filepath); if(statObj.isDirectory()){ let files = awaity readdir(filepath); files.map(file=>{ name:file ,path:path.join(pathname,file) }); // 讓handlebar 拿著數(shù)去編譯模板 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){ this.sendError(e,req,res); } }
[tip] 我們將 request 方法 async 化,這樣我們就能像寫(xiě)同步代碼一樣寫(xiě)異步
方法
sendFile
涉及緩存、編碼、分段傳輸?shù)裙δ?/p>
sendFile(){ 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); //支持?jǐn)帱c(diǎn)續(xù)傳 if(encoding){ rs.pipe(encoding).pipe(res); }else{ rs.pipe(res); } }
handleCache
緩存處理時(shí)要注意的是,緩存分為強(qiáng)制緩存和對(duì)比緩存,且強(qiáng)制緩存的優(yōu)先級(jí)是高于相對(duì)緩存的。 也就是說(shuō),當(dāng)強(qiáng)制緩存生效的時(shí)候并不會(huì)走相對(duì)緩存,不會(huì)像服務(wù)器發(fā)起請(qǐng)求。 但一旦強(qiáng)制緩存失效,就會(huì)走相對(duì)緩存,如果 文件標(biāo)識(shí) 沒(méi)有改變,則相對(duì)緩存生效, 客戶(hù)端仍然會(huì)去緩存數(shù)據(jù)拿取數(shù)據(jù),所以強(qiáng)制緩存和相對(duì)緩存并不沖突。 強(qiáng)制緩存和相對(duì)緩存一起使用時(shí),能在減少服務(wù)器的壓力的同事又保持請(qǐng)求數(shù)據(jù)的及時(shí)更新。
另外需要注意的是,如果同時(shí)設(shè)置了兩種相對(duì)緩存的文件標(biāo)識(shí),必須要兩種都沒(méi)有改變時(shí),緩存才生效。
handleCache(req,res,filepath,statObj){ let ifModifiedSince = req.headers['if-modified-since']; //第一次請(qǐng)求是不會(huì)有的 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()); //此時(shí)間必須為GMT let etag = statObj.size; let lastModified = statObj.ctime.toGMTString(); //此時(shí)間格式可配置 res.setHeader('Etag',etag); res.setHeader('Last-Modified',lastModified); if(isNoneMatch && isNoneMatch != etag) return false; //若是第一次請(qǐng)求已經(jīng)返回false if(ifModifiedSince && ifModifiedSince != lastModified) return false; if(isNoneMatch || ifModifiedSince){ // 說(shuō)明設(shè)置了isNoneMatch或則isModifiedSince且文件沒(méi)有改變 res.writeHead(304); res.end(); return true; }esle{ return false; } }
getEncoding
從請(qǐng)求頭中拿取到瀏覽器能接收的編碼類(lèi)型,利用正則匹配匹配出最前面那個(gè), 創(chuàng)建出對(duì)應(yīng)的zlib實(shí)例返回給sendFile方法,以便在返回文件時(shí)進(jìn)行編碼。
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; } }
getStream
分段傳輸,主要利用的是請(qǐng)求頭中的 req.headers['range']
來(lái)確認(rèn)要接收的文件是從哪里開(kāi)始到哪里結(jié)束,然而真正拿到這部分?jǐn)?shù)據(jù)是通過(guò) fs.createReadStream
來(lái)讀取到的。
getStream(req,res,filepath,statObj){ let start = 0; let end = startObj.size - 1; let range = req.headers['range']; if(range){ res.setHeader('Accept-Range','bytes'); res.statusCode = 206; //返回整個(gè)數(shù)據(jù)的一塊 let result = range.match(/bytes = (\d*)-(\d*)/); //不可能有小數(shù),網(wǎng)絡(luò)傳輸?shù)淖钚挝粸橐粋€(gè)字節(jié) if(result){ start = isNaN(result[1])?0:parseInt(result[1]); end = isNaN(result[2])?end:parseInt(result[2])-1; //因?yàn)閞eadstream的索引是包前又包后故要減去1 } } return fs.createReadStream(filepath,{ start,end }); }
包裝成命令行工具
我們可以像在命令行中輸入 npm start
啟動(dòng)一個(gè)dev-server
一樣自定義一個(gè)啟動(dòng)命令來(lái)啟動(dòng)我們的靜態(tài)服務(wù)器。
大體實(shí)現(xiàn)的思路是: 在 packge.json 中的 bin 屬性下配置一個(gè)啟動(dòng)命令和這個(gè)執(zhí)行這個(gè)命令的文件的路徑。 然后我們需要準(zhǔn)備一個(gè)批處理文件,在文件中引入我們的靜態(tài)服務(wù)器文件,讓我們的服務(wù)器跑起來(lái) 然后將這個(gè)文件 node link 即可。
總結(jié)
以上所述是小編給大家介紹的Node.js靜態(tài)服務(wù)器的實(shí)現(xiàn)方法,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)創(chuàng)新互聯(lián)網(wǎng)站的支持!
本文標(biāo)題:Node.js靜態(tài)服務(wù)器的實(shí)現(xiàn)方法
轉(zhuǎn)載注明:http://aaarwkj.com/article28/goojjp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、外貿(mào)網(wǎng)站建設(shè)、響應(yīng)式網(wǎng)站、靜態(tài)網(wǎng)站、網(wǎng)站策劃、網(wǎng)站收錄
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)