數(shù)據(jù)上傳
單純的頭部報(bào)文無(wú)法攜帶大量的數(shù)據(jù),在業(yè)務(wù)中郭宝,我們往往需要接收一些數(shù)據(jù)柜与,比如表單提交、文件提交惹想、JSON上傳、XML上傳等饵婆。
如果請(qǐng)求中還帶有內(nèi)容部分(如POST請(qǐng)求勺馆,它具有報(bào)頭和內(nèi)容)戏售,內(nèi)容部分需要用戶自行接收和解析,通過報(bào)頭的Transfer-Encoding
或 Content-Length
即可判斷請(qǐng)求中是否帶有內(nèi)容名草穆。
var hasBody = function(req) {
return 'transfer-encoding' in req.headers || 'content-length' in req.headers;
};
表單數(shù)據(jù)
默認(rèn)的表單提交灌灾,請(qǐng)求頭中的Content-Type字段值為application/x-www-form-urlencoded
,由于它的報(bào)文體內(nèi)容跟查詢字符串相同foo=bar&baz=val
悲柱,因此可以直接使用queryString解析:
if (req.headers['content-type'] === 'application/x-www-form-urlencoded') {
req.body = querystring.parse(req.rawBody);
}
其他格式
常見的提交還有JSON和XML文件等锋喜,判斷它們都是依據(jù)Content-Type字段,其中JSON類型的值為application/json
豌鸡,XML的值為application/xml
嘿般。需要注意的是,在Content-Type中可能還附帶如下所示的編碼信息涯冠。
Content-Type: application/json; charset=utf-8
使用JSON.parse()解析JSON炉奴,使用第三方庫(kù)解析XML
var xml2js = require('xml2js');
var handle = function (req, res) {
if (mime(req) === 'application/xml') {
xml2js.parseString(req.rawBody, function (err, xml) {
if (err) {
// 異常內(nèi)容,響應(yīng)Bad request
res.writeHead(400);
res.end('Invalid XML');
return;
}
req.body = xml;
todo(req, res);
});
} else if (mime(req) === 'application/json') {
try {
req.body = JSON.parse(req.rawBody);
} catch (e) {
// 異常內(nèi)容蛇更,響應(yīng)Bad request
res.writeHead(400);
res.end('Invalid JSON');
return;
}
}
};
附件上傳
在前端HTML代碼中瞻赶,特殊表單與普通表單的差異在于該表單中可以含有file類型的控件,以及需要指定表單屬性enctype為multipart/form-data
派任。
瀏覽器在遇到multipart/form-data
表單提交時(shí)砸逊,構(gòu)造的請(qǐng)求報(bào)文與普通報(bào)文完全不同,首先它的報(bào)頭中最為特殊的如下所示:
Content-Type: multipart/form-data; boundary=AaB03x
Content-Length: 18231
它代表本次提交的內(nèi)容是多部分構(gòu)成的掌逛,其中boundary=AaB03x指定的是每部分內(nèi)容的分界符师逸,AaB03x是隨機(jī)生成的一段字符串,報(bào)文體的內(nèi)容將通過在它前面添加--進(jìn)行分割豆混,報(bào)文結(jié)束時(shí)在它前后都加上--標(biāo)識(shí)結(jié)束篓像,另外Content-Length的值必須確保是報(bào)文體的長(zhǎng)度。
數(shù)據(jù)上傳與安全
內(nèi)存限制
在解析表單崖叫、JSON和XML部分遗淳,我們采取的策略是先保存用戶提交的所有數(shù)據(jù),然后再解析處理心傀,最后才傳遞給業(yè)務(wù)邏輯屈暗。這種策略存在的問題就是,僅僅適合數(shù)據(jù)量小的提交請(qǐng)求脂男,如果攻擊者每次提交1MB的內(nèi)容养叛,那么內(nèi)存很快就會(huì)被占光。
解決這個(gè)問題有兩個(gè)方案:
1.限制上傳內(nèi)容的大小宰翅,一旦超過限制弃甥,停止接受數(shù)據(jù),并響應(yīng)400狀態(tài)碼
2.通過流式解析汁讼,將數(shù)據(jù)導(dǎo)向到磁盤中淆攻,Node只保留文件路徑等小數(shù)據(jù)阔墩。
// 限制上傳數(shù)據(jù)量
var bytes = 1024;
function (req, res) {
var received = 0;
var len = req.headers['content-length'] ? parseInt(req.headers['content-length'], 10) : null;
// 內(nèi)容超過長(zhǎng)度限制,返回請(qǐng)求實(shí)體過長(zhǎng)的狀態(tài)碼
if (len && len > bytes) {
res.writeHead(413); res.end();
return;
}
// limit
req.on('data', function (chunk) {
// 內(nèi)容長(zhǎng)度累積超過限制瓶珊。
received += chunk.length;
if (received > bytes) {
// 停止接收數(shù)據(jù)啸箫,觸發(fā)end()
req.destroy();
}
});
handle(req, res);
};
CSRF: Cross-Site Request Forgery
通常而言,用戶通過瀏覽器訪問服務(wù)器端的Session ID是無(wú)法被第三方知道的伞芹,但是CSRF的攻擊者并不需要知道Session ID就能讓用戶中招忘苛。
舉例:用戶C訪問網(wǎng)站A,輸入用戶名密碼成功通過驗(yàn)證唱较,此時(shí)網(wǎng)站A返回Cookie信息給瀏覽器扎唾,用戶可以成功發(fā)送請(qǐng)求到網(wǎng)站A,如果用戶未退出網(wǎng)站A南缓,被攻擊者引誘訪問了攻擊者擁有的網(wǎng)站B胸遇,網(wǎng)站B有一段自執(zhí)行代碼去訪問網(wǎng)站A,此時(shí)用戶與網(wǎng)站A的cookie還未失效西乖,那么就可以成功訪問狐榔,并且這次請(qǐng)求的參數(shù)是由攻擊者決定的坛增,這樣就能以你的名義操作你的賬號(hào)获雕。
解決方案:
- 驗(yàn)證 HTTP Referer 字段
如果黑客要對(duì)網(wǎng)站實(shí)施 CSRF 攻擊,他只能在他自己的網(wǎng)站構(gòu)造請(qǐng)求收捣,當(dāng)用戶通過黑客的網(wǎng)站發(fā)送請(qǐng)求到后臺(tái)時(shí)届案,該請(qǐng)求的 Referer 是指向黑客自己的網(wǎng)站, 然而罢艾,這種方法并非萬(wàn)無(wú)一失楣颠。Referer 的值是由瀏覽器提供的。事實(shí)上咐蚯,對(duì)于某些瀏覽器童漩,比如 IE6 或 FF2已經(jīng)有一些方法可以篡改 Referer 值。即便是使用最新的瀏覽器春锋,黑客無(wú)法篡改 Referer 值矫膨,這種方法仍然有問題。因?yàn)?Referer 值會(huì)記錄下用戶的訪問來源期奔,有些用戶認(rèn)為這樣會(huì)侵犯到他們自己的隱私權(quán)侧馅,特別是有些組織擔(dān)心 Referer 值會(huì)把組織內(nèi)網(wǎng)中的某些信息泄露到外網(wǎng)中。
- 在請(qǐng)求地址中添加 token 并驗(yàn)證
為每一個(gè)請(qǐng)求的用戶呐萌,在Session中賦予一個(gè)隨機(jī)值馁痴,在做頁(yè)面渲染的過程中,將這個(gè)token告知前端肺孤,使得前端才發(fā)送請(qǐng)求的時(shí)候會(huì)攜帶上token罗晕,后端收到請(qǐng)求判斷token是否是自己給前端的济欢,以此辨別該請(qǐng)求是否是偽造的。如果是動(dòng)態(tài)生成的HTML或者是使用前端框架渲染小渊,則需要前端程序員代碼中去處理攜帶上token船逮。
路由解析
MVC
MVC是一個(gè)分層模式,它的工作模式是:
1粤铭、路由解析挖胃,根據(jù)URL尋找到對(duì)應(yīng)的控制器和行為(Controller)
2、行為調(diào)用相關(guān)的模型梆惯,進(jìn)行數(shù)據(jù)操作(Model)
3酱鸭、數(shù)據(jù)操作結(jié)束后,調(diào)用視圖和相關(guān)數(shù)據(jù)進(jìn)行頁(yè)面渲染垛吗,輸出到客戶端凹髓。(View)
如何做到路由映射?
手工映射
var routes = [];
// Controller的集合
var use = function (path, action) {
routes.push([path, action]);
};
function (req, res) {
var pathname = url.parse(req.url).pathname;
for (var i = 0; i < routes.length; i++) {
var route = routes[i];
if (pathname === route[0]) {
// 根據(jù)URL尋找到對(duì)應(yīng)的控制器和行為
var action = route[1];
action(req, res);
return;
}
}
// 處理404請(qǐng)求
handle404(req, res);
}
上面手工映射的問題有兩個(gè)怯屉,
1蔚舀、采用的是硬匹配,對(duì)于動(dòng)態(tài)路徑不方便處理锨络。/profile/jacksontian
和/profile/hoover
兩個(gè)路徑是一樣的模型赌躺,會(huì)出現(xiàn)重復(fù)的代碼操作。
2羡儿、如果使用正則匹配還需要知道到底匹配到了什么礼患,例如上面的username,到底是jacksontian還是hoover掠归,將其匹配到的內(nèi)容抽取出來設(shè)置到req.params
處缅叠。
自然映射
路由按照一種約定的方式自然而然地實(shí)現(xiàn)路由,例如:
/controller/action/param1/param2/param3
以/user/setting/12/1987
為例虏冻,它會(huì)按照約定去找controllers目錄下的user文件肤粱,將其require出來,調(diào)用這個(gè)文件模塊的setting方法厨相,而其余的值作為參數(shù)傳遞給這個(gè)方法领曼。
function (req, res) {
var pathname = url.parse(req.url).pathname;
var paths = pathname.split('/');
var controller = paths[1] || 'index';
var action = paths[2] || 'index';
var args = paths.slice(3);
var module;
try {
// require的緩存機(jī)制使得只有第一次是阻塞的
module = require('./controllers/' + controller);
} catch (ex) {
handle500(req, res);
return;
}
var method = module[action];
if (method) {
method.apply(null, [req, res].concat(args));
} else {
handle500(req, res);
}
}
RESTful
REST的全稱是Representational State Transfer,中文含義為表現(xiàn)層狀態(tài)轉(zhuǎn)化领铐,它的設(shè)計(jì)哲學(xué)主要是將服務(wù)端提供的內(nèi)容實(shí)體看做一個(gè)資源悯森,并表示在URL上,對(duì)這個(gè)資源的操作绪撵,主要體現(xiàn)在HTTP請(qǐng)求方法上瓢姻,而不是原來的URL上。
對(duì)一個(gè)資源的增刪查改音诈,采用同一個(gè)URL幻碱,但是請(qǐng)求方式不同绎狭,后端根據(jù)不同的請(qǐng)求方式,做出不同的操作褥傍。
PUT /user/jacksontian
DELETE /user/jacksontian
GET /user/jacksontian
POST /user/jacksontian
過去設(shè)計(jì)資源的格式與后綴有很大的關(guān)聯(lián)儡嘶,在RESTful設(shè)計(jì)中,資源的具體格式由請(qǐng)求報(bào)頭中的Accept
字段和服務(wù)器端的支持情況來決定恍风,如果客戶端同時(shí)接受JSON和XML格式的響應(yīng)蹦狂,那么它的Accept
字段是:Accept: application/json,application/xml
,服務(wù)器根據(jù)自己能夠響應(yīng)的格式做出響應(yīng)朋贬,在響應(yīng)中通過Content-Type
字段告知客戶端是什么格式:Content-Type: application/json
凯楔,所以RESTful設(shè)計(jì)就是,通過URL設(shè)計(jì)資源锦募、請(qǐng)求方法定義資源的操作摆屯、通過Accept決定資源的表現(xiàn)形式。
中間件
對(duì)于web應(yīng)用而言糠亩,我們希望不用接觸到這么多細(xì)節(jié)性的處理虐骑,為此我們引入了中間件(middleware)來簡(jiǎn)化和隔離這些基礎(chǔ)設(shè)施和業(yè)務(wù)邏輯之間的細(xì)節(jié),讓開發(fā)者能夠關(guān)注在業(yè)務(wù)的開發(fā)上赎线,以達(dá)到提升開發(fā)效率的目的廷没。
中間件用來幫我們完成基本功能,例如查詢字符串的解析氛驮,cookie和session的處理腕柜,并把處理結(jié)果反映在req上傳遞下去,由于Node異步的原因矫废,我們需要提供一種機(jī)制,在當(dāng)前中間件處理完成后砰蠢,通知下一個(gè)中間件執(zhí)行蓖扑。
var handle = function (req, res, stack) {
var next = function () {
// 從stack數(shù)組中取出中間件并執(zhí)行
var middleware = stack.shift();
if (middleware) {
// 傳入next()函數(shù)自身,使中間件能夠執(zhí)行結(jié)束后遞歸
middleware(req, res, next);
}
};
// 啟動(dòng)執(zhí)行
next();
}
這里帶來的疑問是台舱,像querystring律杠、cookie、session這樣基礎(chǔ)的功能中間件是否需要為每一個(gè)路由都進(jìn)行設(shè)置呢竞惋?如果都設(shè)置將會(huì)演變成如下的路由配置:
app.get('/user/:username', querystring, cookie, session, getUser);
app.put('/user/:username', querystring, cookie, session, updateUser);
這并不是一個(gè)好的設(shè)計(jì)柜去,我們需要將路由和中間件結(jié)合起來。
app.use(querystring);
app.use(cookie);
app.use(session);
app.get('/user/:username', getUser);
app.put('/user/:username', authorize, updateUser);
app.use = function (path) {
var handle;
// 如果第一參數(shù)是路徑
if (typeof path === 'string') {
handle = {
path: pathRegexp(path),
stack: Array.prototype.slice.call(arguments, 1)
};
} else {
// 如果不是路徑拆宛,則相當(dāng)于是對(duì)所有路徑進(jìn)行中間件的處理
// 之后的所有請(qǐng)求都可以拿到處理后的結(jié)果嗓奢,用與不用,取決于業(yè)務(wù)邏輯浑厚。
handle = {
path: pathRegexp('/'),
stack: Array.prototype.slice.call(arguments, 0)
};
}
routes.all.push(handle);
};
異常處理
如果某一個(gè)中間件出現(xiàn)錯(cuò)誤該怎么辦股耽,我們需要為自己構(gòu)建的web應(yīng)用的穩(wěn)定性和健壯性負(fù)責(zé)根盒,應(yīng)該為next()方法添加err參數(shù),并捕獲中間件直接拋出的同步異常物蝙。
var handle = function (req, res, stack) {
var next = function (err) {
if (err) {
return handle500(err, req, res, stack);
}
var middleware = stack.shift();
if (middleware) {
try {
middleware(req, res, next);
} catch (ex) {
next(err);
}
}
};
// 啟動(dòng)執(zhí)行
next();
};
由于異步方法的異常不能直接捕獲炎滞,因?yàn)楫惒秸{(diào)用都是直接返回,使用try/catch無(wú)法捕獲到請(qǐng)求階段的異常诬乞,所以需要在異步執(zhí)行完的回調(diào)中將異常手動(dòng)傳遞出來册赛。
var session = function (req, res, next) {
var id = req.cookies.sessionid;
store.get(id, function (err, session) {
if (err) {
// 將異常通過next()傳遞
return next(err);
}
req.session = session;
next();
});
}
中間件與性能
使用中間件,我們可以發(fā)現(xiàn)業(yè)務(wù)邏輯往往是最后執(zhí)行震嫉,為了讓業(yè)務(wù)邏輯提早執(zhí)行击奶,盡早響應(yīng)給終端用戶,中間件的編寫和使用是需要一番考究的责掏,下面是兩個(gè)主要提升的點(diǎn):
編寫高效的中間件
編寫高效的中間件其實(shí)就是提升單個(gè)處理單元的處理速度柜砾,以盡早調(diào)用next()執(zhí)行后續(xù)邏輯,因?yàn)橹虚g件一旦匹配换衬,那么每個(gè)請(qǐng)求都會(huì)使該中間件執(zhí)行一次痰驱,哪怕只浪費(fèi)1毫秒的執(zhí)行時(shí)間,都會(huì)讓我們的QPS顯著下降(一秒內(nèi)可以處理的請(qǐng)求數(shù)量稱之為服務(wù)器的QPS)
常見的優(yōu)化方法有:
1瞳浦、使用已有API中高效的方法担映,而不是自己嘗試編寫。
2叫潦、緩存需要重復(fù)計(jì)算的結(jié)果
3蝇完、避免不必要的計(jì)算,比如HTTP報(bào)文體的解析矗蕊,對(duì)于GET方法完全不需要短蜕。
合理使用路由,避免不必要的中間件的執(zhí)行
合理的路由使得不必要的中間件不參與請(qǐng)求處理的過程傻咖,假設(shè)我們這里有一個(gè)靜態(tài)文件的中間件朋魔,它會(huì)對(duì)請(qǐng)求進(jìn)行判斷,如果磁盤上存在對(duì)應(yīng)文件卿操,就響應(yīng)對(duì)應(yīng)的靜態(tài)文件警检,否則就交給下游中間件處理。
var staticFile = function (req, res, next) {
var pathname = url.parse(req.url).pathname;
fs.readFile(path.join(ROOT, pathname), function (err, file) {
if (err) {
return next();
}
res.writeHead(200);
res.end(file);
});
};
如果我們以如下方式注冊(cè)路由app.use(staticFile);
害淤,那么意味著對(duì)根路徑下所有的URL請(qǐng)求都會(huì)進(jìn)行判斷扇雕,對(duì)于這種情況,我們需要做的是提升匹配成功率app.use('/public', staticFile);
窥摄,這樣只有/public
路徑會(huì)匹配上镶奉,其他路徑就不會(huì)涉及到該中間件。
頁(yè)面渲染
內(nèi)容響應(yīng)
服務(wù)器端響應(yīng)的報(bào)文,最終都要被終端處理腮鞍,這個(gè)終端可能是命令行終端值骇,也可能是代碼終端,也可能是瀏覽器終端移国。服務(wù)器端的響應(yīng)從一定程度上決定了客戶端該如何處理響應(yīng)的內(nèi)容吱瘩。
瀏覽器通過Content-Type的值來決定采用不同的渲染方式,這個(gè)值我們簡(jiǎn)稱為MIME(Multipurpose Internet Mail Extensions)迹缀,最早用于電子郵件使碾,后來應(yīng)用到瀏覽器中,不同的文件類型具有不同的MIME值祝懂,為了方便獲知文件的MIME值票摇,社區(qū)有專有的mime模塊可以用來判斷文件類型。
var mime = require('mime');
mime.lookup('/path/to/file.txt'); // => 'text/plain'
mime.lookup('file.txt'); // => 'text/plain'
mime.lookup('.TXT'); // => 'text/plain'
mime.lookup('htm'); // => 'text/html
// JSON application/json
// XML application/xml
// PDF application/pdf
// ...
除了MIME值外砚蓬,Content-Type的值還可以包含一些參數(shù)矢门,如字符集:
Content-Type: text/javascript; charset=utf-8
附件下載
在一些場(chǎng)景下,無(wú)論響應(yīng)的內(nèi)容是什么樣的MIME值灰蛙,需求中并不要求客戶端去打開它祟剔,只需彈出并下載它即可,Content-Disposition
字段就是為了滿足這種需求摩梧,客戶端會(huì)根據(jù)它的值判斷是應(yīng)該將報(bào)文數(shù)據(jù)當(dāng)做即時(shí)瀏覽的內(nèi)容物延,還是可下載的附件,當(dāng)內(nèi)容值需即時(shí)查看時(shí)仅父,它的值為inline
叛薯,當(dāng)數(shù)據(jù)可以存為附件時(shí),它的值為attachment
笙纤,另外Content-Disposition
還能通過參數(shù)指定保存時(shí)應(yīng)該使用的文件名耗溜。
Content-Disposition: attachment; filename="filename.ext"
如果我們要設(shè)計(jì)一個(gè)響應(yīng)附件下載的API,大致如下:
res.sendfile = function (filepath) {
fs.stat(filepath, function(err, stat) {
var stream = fs.createReadStream(filepath);
// 設(shè)置內(nèi)容
res.setHeader('Content-Type', mime.lookup(filepath));
// 設(shè)置長(zhǎng)度
res.setHeader('Content-Length', stat.size);
// 設(shè)置為附件
res.setHeader('Content-Disposition' 'attachment; filename="' + path.basename(filepath) + '"');
res.writeHead(200);
stream.pipe(res);
});
};
響應(yīng)JSON
res.json = function (json) {
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(JSON.stringify(json));
};
響應(yīng)跳轉(zhuǎn)
res.redirect = function (url) {
res.setHeader('Location', url);
res.writeHead(302);
res.end('Redirect to ' + url);
};
視圖渲染
模板是帶有特殊標(biāo)簽的HTML片段粪糙,通過與數(shù)據(jù)的渲染强霎,將數(shù)據(jù)填充到這些特殊標(biāo)簽中,最后生成普通的帶數(shù)據(jù)的HTML片段蓉冈,通常我們將渲染方法設(shè)計(jì)為render(),參數(shù)就是模板路徑和數(shù)據(jù)轩触。
res.render = function (view, data) {
res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
// 實(shí)際渲染
var html = render(view, data);
res.end(html);
};
模板
為了使HTML與邏輯代碼分離開來寞酿,催生了一些服務(wù)器端動(dòng)態(tài)網(wǎng)頁(yè)技術(shù),如ASP脱柱、PHP伐弹、JSP。它們將動(dòng)態(tài)語(yǔ)言部分通過特殊的標(biāo)簽(ASP和JSP以<% %>
作為標(biāo)志榨为,PHP則以<? ?>
作為標(biāo)志)包含起來惨好,通過HTML和模板標(biāo)簽混排煌茴,將開發(fā)者從輸出HTML的工作中解脫出來,這種方法雖然一定程度上減輕了開發(fā)維護(hù)的難度日川,但是頁(yè)面還是充斥著大量的邏輯代碼蔓腐,這催生了MVC在動(dòng)態(tài)網(wǎng)頁(yè)技術(shù)中的發(fā)展,MVC將邏輯龄句、顯示回论、數(shù)據(jù)分離開來的方式,大大提高了項(xiàng)目的可維護(hù)性分歇。
模板技術(shù)雖然多種多樣傀蓉,但它的實(shí)質(zhì)就是將模板文件和數(shù)據(jù)通過模板引擎生成最終的HTML代碼,形成模板技術(shù)的也就如下4個(gè)要素:模板語(yǔ)言职抡、包含模板語(yǔ)言的模板文件葬燎、擁有動(dòng)態(tài)數(shù)據(jù)的數(shù)據(jù)對(duì)象、模板引擎缚甩。模板技術(shù)使得網(wǎng)頁(yè)中的動(dòng)態(tài)內(nèi)容和靜態(tài)內(nèi)容變得不互相依賴谱净,數(shù)據(jù)開發(fā)者和模板開發(fā)者只要約定好數(shù)據(jù)結(jié)構(gòu),兩者就不用互相影響了蹄胰。
但模板技術(shù)并不是什么神秘的技術(shù)岳遥,它干的實(shí)際上是拼接字符傳這樣和底層的活,做的就是替換特殊標(biāo)簽的技術(shù)裕寨,只是各種模板有著各自的優(yōu)缺點(diǎn)和技巧浩蓉。
模板安全
模板技術(shù)用來替換特殊標(biāo)簽然后形成新的HTML代碼,這樣就很容易形成XSS攻擊宾袜,例如將某個(gè)數(shù)據(jù)的值改為<script>alert("I am XSS.")</script>
捻艳,如果渲染到頁(yè)面上,這段腳本就會(huì)被執(zhí)行庆猫,為了提高安全性认轨,大多數(shù)模板提供了轉(zhuǎn)義的功能,轉(zhuǎn)義就是將能形成HTML標(biāo)簽的字符轉(zhuǎn)換成安全的字符月培,這些字符主要有&
嘁字、<
、>
杉畜、"
纪蜒、'
var escape = function (html) {
return String(html)
.replace(/&(?!\w+;)/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};
集成文件系統(tǒng)
var cache = {};
var VIEW_FOLDER = '/path/to/wwwroot/views';
res.render = function (viewname, data) {
if (!cache[viewname]) {
var text;
try {
text = fs.readFileSync(path.join(VIEW_FOLDER, viewname), 'utf8');
} catch (e) {
res.end('模板文件錯(cuò)誤');
return;
}
cache[viewname] = complie(text);
}
var complied = cache[viewname];
res.writeHead(200, {'Content-Type': 'text/html'});
var html = complied(data);
res.end(html);
};
這個(gè)res.render()的實(shí)現(xiàn)中,雖然有同步讀取文件的情況此叠,但是由于采用了緩存纯续,只會(huì)在第一次讀取的時(shí)候造成整個(gè)進(jìn)程的阻塞,一旦緩存生效,將不會(huì)反復(fù)讀取文件猬错,其次窗看,緩存之前已經(jīng)進(jìn)行了編譯,也不會(huì)每次讀取都編譯倦炒,并且與文件系統(tǒng)集成了显沈,調(diào)用的時(shí)候只需要指定模板文件的名稱和數(shù)據(jù)即可完成渲染。
app.get('/aaa', function (req, res) {
res.render('viewname', {});
});