這半個多月一直在學(xué)Node.js,還是在入門階段人乓,不過已經(jīng)對Node很感興趣了勤篮。這里介紹一個簡單的靜態(tài)文件服務(wù)器,總結(jié)一下心得體會色罚。為什么說簡單呢碰缔,因為雖然基本功能都有,但是沒加路由保屯,還有沒考慮一些安全性的東西手负。后面我會繼續(xù)來總結(jié)完善。
本文參考了Node大神樸靈11年寫的一篇博文(強烈建議讀一讀姑尺,雖然有一些接口有些老竟终,但是搭建服務(wù)器的思路,各方面都有涉及)切蟋,還有這位大神前輩的博客统捶,以及Node.js 6.x版本的文檔,stackoverflow一些答案柄粹。
如果哪里說得不對或者哪里有問題喘鸟,請勞煩您指正,我會虛心接受驻右。
正文如下:
一個Web服務(wù)器應(yīng)具備以下幾個功能:
1什黑、能顯示以.html/.htm結(jié)尾的Web頁面
2、能直接打開以.js/.css/.json/.text結(jié)尾的文件內(nèi)容
3堪夭、顯示圖片資源
4愕把、自動下載以.apk/.docx/.zip結(jié)尾的文件
5、形如http://xxx.com/a/b/ , 則查找b目錄下是否有index.html,如果有就顯示森爽,如果沒有就列出該目錄下的所有文件及文件夾恨豁,并可以進(jìn)一步訪問。
6爬迟、形如http://xxx.com/a/b, 則作301重定向到http://xxx.com/a/b/ , 這樣可以解決內(nèi)部資源引用錯位的問題橘蜜。
總體的思路如下:
1.先加載需要用到的幾個模塊 url(解析request,截取路徑) path(解析路徑) fs(文件讀寫操作) http(起服務(wù)器)
2.切出來請求的url和路徑 (記得解碼,防止中文亂碼)
3.根據(jù)路徑有無擴展名以及是否以“/”結(jié)尾作判斷付呕,重定向
4.根據(jù)路徑來查找資源文件
OK,接下來上代碼计福,我有詳細(xì)的標(biāo)注注釋(咳,方便以后來查漏補缺)
前面有提到參考的那兩篇博文的年代有些久遠(yuǎn)(其實也就4,5年前..)徽职,以致一些接口6.x版本的Node.js已經(jīng)不支持了棒搜,或者一些方法近些年有了最佳實踐。
說一下重構(gòu)的地方:
1.判斷路徑類型活箕,不使用fs.exist(),而是換成fs.stat方法
2.將回調(diào)換成了箭頭函數(shù)。
3.我一直沒查到getContentType這個方法.. 所以還是使用mime映射來傳入content-type 其實已經(jīng)專門有mime模塊來處理這個問題了育韩。
4.做了一下模塊化處理
第一個模塊: app.js 啟動模塊
"use strict";
//加載所需要的模塊
var http = require('http');
var processRequest = require('./server');
//創(chuàng)建服務(wù)克蚂,這里很機智的把對response和request的處理封裝成一個匿名函數(shù),傳入createServer中
//也可以直接在里面寫筋讨,但是看起來不是很整潔
var httpServer = http.createServer((req, res) => {
processRequest(req, res);
});
var port = 8080;
//指定一個監(jiān)聽的接口
httpServer.listen(port, function() {
console.log(`app is running at port:${port}`);
});
第二個模塊:mime.js 存放類型映射
module.exports = {
"css": "text/css",
"gif": "image/gif",
"html": "text/html",
"ico": "image/x-icon",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"js": "text/javascript",
"json": "application/json",
"pdf": "application/pdf",
"png": "image/png",
"svg": "image/svg+xml",
"swf": "application/x-shockwave-flash",
"tiff": "image/tiff",
"txt": "text/plain",
"wav": "audio/x-wav",
"wma": "audio/x-ms-wma",
"wmv": "video/x-ms-wmv",
"xml": "text/xml"
};
第三個模塊埃叭,也是我們的核心模塊 server.js
var url = require('url');
var fs = require('fs');
var path = require('path');
var mime = require('./mime');
function processRequest(request, response) {
//request里面切出標(biāo)識符字符串
var requestUrl = request.url;
//url模塊的parse方法 接受一個字符串,返回一個url對象,切出來路徑
var pathName = url.parse(requestUrl).pathname;
//對路徑解碼悉罕,防止中文亂碼
var pathName = decodeURI(pathName);
//解決301重定向問題赤屋,如果pathname沒以/結(jié)尾,并且沒有擴展名
if (!pathName.endsWith('/') && path.extname(pathName) === '') {
pathName += '/';
var redirect = "http://" + request.headers.host + pathName;
response.writeHead(301, {
location: redirect
});
//response.end方法用來回應(yīng)完成后關(guān)閉本次對話壁袄,也可以寫入HTTP回應(yīng)的具體內(nèi)容类早。
response.end();
};
//獲取資源文件的絕對路徑
var filePath = path.resolve(__dirname + pathName);
console.log(filePath);
//獲取對應(yīng)文件的文檔類型
//我們通過path.extname來獲取文件的后綴名。由于extname返回值包含”.”嗜逻,所以通過slice方法來剔除掉”.”涩僻,
//對于沒有后綴名的文件,我們一律認(rèn)為是unknown栈顷。
var ext = path.extname(pathName);
ext = ext ? ext.slice(1) : 'unknown';
//未知的類型一律用"text/plain"類型
var contentType = mime[ext] || "text/plain";
fs.stat(filePath, (err, stats) => {
if (err) {
response.writeHead(404, { "content-type": "text/html" });
response.end("<h1>404 Not Found</h1>");
};
//沒出錯 并且文件存在
if (!err && stats.isFile()) {
response.writeHead(200, { "content-type": contentType });
//建立流對象逆日,讀文件
var stream = fs.createReadStream(filePath);
//錯誤處理
stream.on('error', function() {
response.writeHead(500, { "content-type": contentType });
response.end("<h1>500 Server Error</h1>");
});
//讀取文件
stream.pipe(response);
//response.end(); 這個地方有坑,加了會關(guān)閉對話萄凤,看不到內(nèi)容了
};
//如果路徑是目錄
if (!err && stats.isDirectory()) {
var html = " <head><meta charset = 'utf-8'/></head>";
//讀取該路徑下文件
fs.readdir(filePath, (err, files) => {
if (err) {
console.log("讀取路徑失斒页椤!");
} else {
// files.foreach(function (file) {
// //做成一個鏈接表靡努,方便用戶訪問
// html+=`<div><a href="${file}">${file}</a></div>`;
// });
for (var file of files) {
if (file === "index.html") {
response.writeHead(200, { "content-type": "text/html" });
response.end(file);
break;
};
html += `<div><a href='${file}'>${file}</a></div>`;
console.log(html);
}
response.writeHead(200, { "content-type": "text/html" });
response.end(html);
};
});
};
});
};
module.exports = processRequest;
這里有幾個細(xì)節(jié)的地方值得注意坪圾,一個是如何判斷并實現(xiàn)重定向的,第二個是如何來判斷content-type的颤难。第三個要注意異步回調(diào)的執(zhí)行順序問題神年,有個坑就在傳html那里。
再說一個小坑吧行嗤,response.end()這個方法我理解的不夠深已日,這個方法是用來關(guān)閉對話或者向response的body部分傳內(nèi)容,我把它放在了stream操作的下面栅屏,結(jié)果可想而知... 所以再涉及到文件讀寫時飘千,一定要注意這個地方。
下面是實現(xiàn)截圖:
1.這是我的目錄栈雳,不用管那個vscode护奈,那是visual studio code文件配置目錄

2.服務(wù)器啟動在8080端口

3.使用這個地址

4.打開調(diào)試工具,可以發(fā)現(xiàn)Head部分是301哥纫,也就是發(fā)生了重定向

5.來看看網(wǎng)頁內(nèi)容霉旗,可以看到,我們得到了該目錄下可以訪問的文件條目

6.最后,控制臺信息

OK 就是這樣厌秒。歡迎收看你的月亮我的心读拆,好男人就是我~ 我們下周同一時間再會~
PS:如果本文有錯誤的地方請您一定要指出來,我會虛心接受鸵闪,萬分感謝檐晕。~~