国产99久久精品_欧美日本韩国一区二区_激情小说综合网_欧美一级二级视频_午夜av电影_日本久久精品视频

最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
當(dāng)前位置: 首頁 - 科技 - 知識(shí)百科 - 正文

使用Node.js實(shí)現(xiàn)簡易MVC框架的方法

來源:懂視網(wǎng) 責(zé)編:小采 時(shí)間:2020-11-27 22:33:24
文檔

使用Node.js實(shí)現(xiàn)簡易MVC框架的方法

使用Node.js實(shí)現(xiàn)簡易MVC框架的方法:在使用Node.js搭建靜態(tài)資源服務(wù)器一文中我們完成了服務(wù)器對(duì)靜態(tài)資源請(qǐng)求的處理,但并未涉及動(dòng)態(tài)請(qǐng)求,目前還無法根據(jù)客戶端發(fā)出的不同請(qǐng)求而返回個(gè)性化的內(nèi)容。單靠靜態(tài)資源豈能撐得起這些復(fù)雜的網(wǎng)站應(yīng)用,本文將介紹如何使用Node處理動(dòng)態(tài)請(qǐng)求,以及如何搭建
推薦度:
導(dǎo)讀使用Node.js實(shí)現(xiàn)簡易MVC框架的方法:在使用Node.js搭建靜態(tài)資源服務(wù)器一文中我們完成了服務(wù)器對(duì)靜態(tài)資源請(qǐng)求的處理,但并未涉及動(dòng)態(tài)請(qǐng)求,目前還無法根據(jù)客戶端發(fā)出的不同請(qǐng)求而返回個(gè)性化的內(nèi)容。單靠靜態(tài)資源豈能撐得起這些復(fù)雜的網(wǎng)站應(yīng)用,本文將介紹如何使用Node處理動(dòng)態(tài)請(qǐng)求,以及如何搭建

在使用Node.js搭建靜態(tài)資源服務(wù)器一文中我們完成了服務(wù)器對(duì)靜態(tài)資源請(qǐng)求的處理,但并未涉及動(dòng)態(tài)請(qǐng)求,目前還無法根據(jù)客戶端發(fā)出的不同請(qǐng)求而返回個(gè)性化的內(nèi)容。單靠靜態(tài)資源豈能撐得起這些復(fù)雜的網(wǎng)站應(yīng)用,本文將介紹如何使用Node處理動(dòng)態(tài)請(qǐng)求,以及如何搭建一個(gè)簡易的 MVC 框架。因?yàn)榍拔囊呀?jīng)詳細(xì)介紹過靜態(tài)資源請(qǐng)求如何響應(yīng),本文將略過所有靜態(tài)部分。

一個(gè)簡單的示例

先從一個(gè)簡單示例入手,明白在 Node 中如何向客戶端返回動(dòng)態(tài)內(nèi)容。

假設(shè)我們有這樣的需求:

當(dāng)用戶訪問/actors時(shí)返回男演員列表頁

當(dāng)用戶訪問/actresses時(shí)返回女演員列表

可以用以下的代碼完成功能:

const http = require('http');
const url = require('url');

http.createServer((req, res) => {
 const pathName = url.parse(req.url).pathname;
 if (['/actors', '/actresses'].includes(pathName)) {
 res.writeHead(200, {
 'Content-Type': 'text/html'
 });
 const actors = ['Leonardo DiCaprio', 'Brad Pitt', 'Johnny Depp'];
 const actresses = ['Jennifer Aniston', 'Scarlett Johansson', 'Kate Winslet'];
 let lists = [];
 if (pathName === '/actors') {
 lists = actors;
 } else {
 lists = actresses;
 }

 const content = lists.reduce((template, item, index) => {
 return template + `<p>No.${index+1} ${item}</p>`;
 }, `<h1>${pathName.slice(1)}</h1>`);
 res.end(content);
 } else {
 res.writeHead(404);
 res.end('<h1>Requested page not found.</h1>')
 }
}).listen(9527);

上面代碼的核心是路由匹配,當(dāng)請(qǐng)求抵達(dá)時(shí),檢查是否有對(duì)應(yīng)其路徑的邏輯處理,當(dāng)請(qǐng)求匹配不上任何路由時(shí),返回 404。匹配成功時(shí)處理相應(yīng)的邏輯。

simple request

上面的代碼顯然并不通用,而且在僅有兩種路由匹配候選項(xiàng)(且還未區(qū)分請(qǐng)求方法),以及尚未使用數(shù)據(jù)庫以及模板文件的前提下,代碼都已經(jīng)有些糾結(jié)了。因此接下來我們將搭建一個(gè)簡易的MVC框架,使數(shù)據(jù)、模型、表現(xiàn)分離開來,各司其職。

搭建簡易MVC框架

MVC 分別指的是:

M: Model (數(shù)據(jù))

V: View (表現(xiàn))

C: Controller (邏輯)

在 Node 中,MVC 架構(gòu)下處理請(qǐng)求的過程如下:

請(qǐng)求抵達(dá)服務(wù)端

服務(wù)端將請(qǐng)求交由路由處理

路由通過路徑匹配,將請(qǐng)求導(dǎo)向?qū)?yīng)的 controller

controller 收到請(qǐng)求,向 model 索要數(shù)據(jù)

model 給 controller 返回其所需數(shù)據(jù)

controller 可能需要對(duì)收到的數(shù)據(jù)做一些再加工

controller 將處理好的數(shù)據(jù)交給 view

view 根據(jù)數(shù)據(jù)和模板生成響應(yīng)內(nèi)容

服務(wù)端將此內(nèi)容返回客戶端

以此為依據(jù),我們需要準(zhǔn)備以下模塊:

server: 監(jiān)聽和響應(yīng)請(qǐng)求

router: 將請(qǐng)求交由正確的controller處理

controllers: 執(zhí)行業(yè)務(wù)邏輯,從 model 中取出數(shù)據(jù),傳遞給 view

model: 提供數(shù)據(jù)

view: 提供 html

創(chuàng)建如下目錄:

-- server.js
-- lib
 -- router.js
-- views
-- controllers
-- models

server

創(chuàng)建 server.js 文件:

const http = require('http');
const router = require('./lib/router')();

router.get('/actors', (req, res) => {
 res.end('Leonardo DiCaprio, Brad Pitt, Johnny Depp');
});

http.createServer(router).listen(9527, err => {
 if (err) {
 console.error(err);
 console.info('Failed to start server');
 } else {
 console.info(`Server started`);
 }
});

先不管這個(gè)文件里的細(xì)節(jié),router是下面將要完成的模塊,這里先引入,請(qǐng)求抵達(dá)后即交由它處理。

router 模塊

router模塊其實(shí)只需完成一件事,將請(qǐng)求導(dǎo)向正確的controller處理,理想中它可以這樣使用:

const router = require('./lib/router')();
const actorsController = require('./controllers/actors');

router.use((req, res, next) => {
 console.info('New request arrived');
 next()
});

router.get('/actors', (req, res) => {
 actorsController.fetchList();
});

router.post('/actors/:name', (req, res) => {
 actorsController.createNewActor();
});

總的來說,我們希望它同時(shí)支持路由中間件和非中間件,請(qǐng)求抵達(dá)后會(huì)由 router 交給匹配上的中間件們處理。中間件是一個(gè)可訪問請(qǐng)求對(duì)象和響應(yīng)對(duì)象的函數(shù),在中間件內(nèi)可以做的事情包括:

執(zhí)行任何代碼,比如添加日志和處理錯(cuò)誤等

修改請(qǐng)求 (req) 和響應(yīng)對(duì)象 (res),比如從 req.url 獲取查詢參數(shù)并賦值到 req.query

結(jié)束響應(yīng)

調(diào)用下一個(gè)中間件 (next)

Note:

需要注意的是,如果在某個(gè)中間件內(nèi)既沒有終結(jié)響應(yīng),也沒有調(diào)用 next 方法將控制權(quán)交給下一個(gè)中間件, 則請(qǐng)求就會(huì)掛起

__非路由中間件__通過以下方式添加,匹配所有請(qǐng)求:

router.use(fn);

比如上面的例子:

router.use((req, res, next) => {
 console.info('New request arrived');
 next()
});

__路由中間件__通過以下方式添加,以 請(qǐng)求方法和路徑精確匹配:

router.HTTP_METHOD(path, fn)

梳理好了之后先寫出框架:

/lib/router.js

const METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'];

module.exports = () => {
 const routes = [];

 const router = (req, res) => {
 
 };

 router.use = (fn) => {
 routes.push({
 method: null,
 path: null,
 handler: fn
 });
 };

 METHODS.forEach(item => {
 const method = item.toLowerCase();
 router[method] = (path, fn) => {
 routes.push({
 method,
 path,
 handler: fn
 });
 };
 });
};

以上主要是給 router 添加了 use、get、post 等方法,每當(dāng)調(diào)用這些方法時(shí),給 routes 添加一條 route 規(guī)則。

Note:

Javascript 中函數(shù)是一種特殊的對(duì)象,能被調(diào)用的同時(shí),還可以擁有屬性、方法。

接下來的重點(diǎn)在 router 函數(shù),它需要做的是:

從req對(duì)象中取得 method、pathname

依據(jù) method、pathname 將請(qǐng)求與routes數(shù)組內(nèi)各個(gè) route 按它們被添加的順序依次匹配

如果與某個(gè)route匹配成功,執(zhí)行 route.handler,執(zhí)行完后與下一個(gè) route 匹配或結(jié)束流程 (后面詳述)

如果匹配不成功,繼續(xù)與下一個(gè) route 匹配,重復(fù)3、4步驟

 const router = (req, res) => {
 const pathname = decodeURI(url.parse(req.url).pathname);
 const method = req.method.toLowerCase();
 let i = 0;

 const next = () => {
 route = routes[i++];
 if (!route) return;
 const routeForAllRequest = !route.method && !route.path;
 if (routeForAllRequest || (route.method === method && pathname === route.path)) {
 route.handler(req, res, next);
 } else {
 next();
 }
 }

 next();
 };

對(duì)于非路由中間件,直接調(diào)用其 handler。對(duì)于路由中間件,只有請(qǐng)求方法和路徑都匹配成功時(shí),才調(diào)用其 handler。當(dāng)沒有匹配上的 route 時(shí),直接與下一個(gè)route繼續(xù)匹配。

需要注意的是,在某條 route 匹配成功的情況下,執(zhí)行完其 handler 之后,還會(huì)不會(huì)再接著與下個(gè) route 匹配,就要看開發(fā)者在其 handler 內(nèi)有沒有主動(dòng)調(diào)用 next() 交出控制權(quán)了。

在__server.js__中添加一些route:

router.use((req, res, next) => {
 console.info('New request arrived');
 next()
});

router.get('/actors', (req, res) => {
 res.end('Leonardo DiCaprio, Brad Pitt, Johnny Depp');
});

router.get('/actresses', (req, res) => {
 res.end('Jennifer Aniston, Scarlett Johansson, Kate Winslet');
});

router.use((req, res, next) => {
 res.statusCode = 404;
 res.end();
});

每個(gè)請(qǐng)求抵達(dá)時(shí),首先打印出一條 log,接著匹配其他route。當(dāng)匹配上 actors 或 actresses 的 get 請(qǐng)求時(shí),直接發(fā)回演員名字,并不需要繼續(xù)匹配其他 route。如果都沒匹配上,返回 404。

在瀏覽器中依次訪問 http://localhost:9527/erwe、http://localhost:9527/actors、http://localhost:9527/actresses 測試一下:

404

network 中觀察到的結(jié)果符合預(yù)期,同時(shí)后臺(tái)命令行中也打印出了三條 New request arrived語句。

接下來繼續(xù)改進(jìn) router 模塊。

首先添加一個(gè) router.all 方法,調(diào)用它即意味著為所有請(qǐng)求方法都添加了一條 route:

router.all = (path, fn) => {
 METHODS.forEach(item => {
 const method = item.toLowerCase();
 router[method](path, fn);
 })
 };

接著,添加錯(cuò)誤處理。

/lib/router.js

const defaultErrorHander = (err, req, res) => {
 res.statusCode = 500;
 res.end();
};

module.exports = (errorHander) => {
 const routes = [];

 const router = (req, res) => {
 ...
 errorHander = errorHander || defaultErrorHander;

 const next = (err) => {
 if (err) return errorHander(err, req, res);
 ...
 }

 next();
 };

server.js

...
const router = require('./lib/router')((err, req, res) => {
 console.error(err);
 res.statusCode = 500;
 res.end(err.stack);
});
...

默認(rèn)情況下,遇到錯(cuò)誤時(shí)會(huì)返回 500,但開發(fā)者使用 router 模塊時(shí)可以傳入自己的錯(cuò)誤處理函數(shù)將其替代。

修改一下代碼,測試是否能正確執(zhí)行錯(cuò)誤處理:

router.use((req, res, next) => {
 console.info('New request arrived');
 next(new Error('an error'));
});

這樣任何請(qǐng)求都應(yīng)該返回 500:

error stack

繼續(xù),修改 route.path 與 pathname 的匹配規(guī)則。現(xiàn)在我們認(rèn)為只有當(dāng)兩字符串相等時(shí)才讓匹配通過,這沒有考慮到 url 中包含路徑參數(shù)的情況,比如:

localhost:9527/actors/Leonardo

router.get('/actors/:name', someRouteHandler);

這條route應(yīng)該匹配成功才是。

新增一個(gè)函數(shù)用來將字符串類型的 route.path 轉(zhuǎn)換成正則對(duì)象,并存入 route.pattern:

const getRoutePattern = pathname => {
 pathname = '^' + pathname.replace(/(\:\w+)/g, '\(\[a-zA-Z0-9-\]\+\\s\)') + '$';
 return new RegExp(pathname);
};

這樣就可以匹配上帶有路徑參數(shù)的url了,并將這些路徑參數(shù)存入 req.params 對(duì)象:

 const matchedResults = pathname.match(route.pattern);
 if (route.method === method && matchedResults) {
 addParamsToRequest(req, route.path, matchedResults);
 route.handler(req, res, next);
 } else {
 next();
 }
const addParamsToRequest = (req, routePath, matchedResults) => {
 req.params = {};
 let urlParameterNames = routePath.match(/:(\w+)/g);
 if (urlParameterNames) {
 for (let i=0; i < urlParameterNames.length; i++) {
 req.params[urlParameterNames[i].slice(1)] = matchedResults[i + 1];
 }
 }
}

添加個(gè) route 測試一下:

router.get('/actors/:year/:country', (req, res) => {
 res.end(`year: ${req.params.year} country: ${req.params.country}`);
});

訪問http://localhost:9527/actors/1990/China試試:

url parameters

router 模塊就寫到此,至于查詢參數(shù)的格式化以及獲取請(qǐng)求主體,比較瑣碎就不試驗(yàn)了,需要可以直接使用 bordy-parser 等模塊。

現(xiàn)在我們已經(jīng)創(chuàng)建好了router模塊,接下來將 route handler 內(nèi)的業(yè)務(wù)邏輯都轉(zhuǎn)移到 controller 中去。

修改__server.js__,引入 controller:

...
const actorsController = require('./controllers/actors');
...
router.get('/actors', (req, res) => {
 actorsController.getList(req, res);
});

router.get('/actors/:name', (req, res) => {
 actorsController.getActorByName(req, res);
});

router.get('/actors/:year/:country', (req, res) => {
 actorsController.getActorsByYearAndCountry(req, res);
});
...

新建__controllers/actors.js__:

const actorsTemplate = require('../views/actors-list');
const actorsModel = require('../models/actors');

exports.getList = (req, res) => {
 const data = actorsModel.getList();
 const htmlStr = actorsTemplate.build(data);
 res.writeHead(200, {
 'Content-Type': 'text/html'
 });
 res.end(htmlStr);
};

exports.getActorByName = (req, res) => {
 const data = actorsModel.getActorByName(req.params.name);
 const htmlStr = actorsTemplate.build(data);
 res.writeHead(200, {
 'Content-Type': 'text/html'
 });
 res.end(htmlStr);
};

exports.getActorsByYearAndCountry = (req, res) => {
 const data = actorsModel.getActorsByYearAndCountry(req.params.year, req.params.country);
 const htmlStr = actorsTemplate.build(data);
 res.writeHead(200, {
 'Content-Type': 'text/html'
 });
 res.end(htmlStr);
};

在 controller 中同時(shí)引入了 view 和 model, 其充當(dāng)了這二者間的粘合劑。回顧下 controller 的任務(wù):

controller 收到請(qǐng)求,向 model 索要數(shù)據(jù)
model 給 controller 返回其所需數(shù)據(jù)
controller 可能需要對(duì)收到的數(shù)據(jù)做一些再加工
controller 將處理好的數(shù)據(jù)交給 view

在此 controller 中,我們將調(diào)用 model 模塊的方法獲取演員列表,接著將數(shù)據(jù)交給 view,交由 view 生成呈現(xiàn)出演員列表頁的 html 字符串。最后將此字符串返回給客戶端,在瀏覽器中呈現(xiàn)列表。

從 model 中獲取數(shù)據(jù)

通常 model 是需要跟數(shù)據(jù)庫交互來獲取數(shù)據(jù)的,這里我們就簡化一下,將數(shù)據(jù)存放在一個(gè) json 文件中。

/models/test-data.json

[
 {
 "name": "Leonardo DiCaprio",
 "birth year": 1974,
 "country": "US",
 "movies": ["Titanic", "The Revenant", "Inception"]
 },
 {
 "name": "Brad Pitt",
 "birth year": 1963,
 "country": "US",
 "movies": ["Fight Club", "Inglourious Basterd", "Mr. & Mrs. Smith"]
 },
 {
 "name": "Johnny Depp",
 "birth year": 1963,
 "country": "US",
 "movies": ["Edward Scissorhands", "Black Mass", "The Lone Ranger"]
 }
]

接著就可以在 model 中定義一些方法來訪問這些數(shù)據(jù)。

models/actors.js

const actors = require('./test-data');

exports.getList = () => actors;

exports.getActorByName = (name) => actors.filter(actor => {
 return actor.name == name;
});

exports.getActorsByYearAndCountry = (year, country) => actors.filter(actor => {
 return actor["birth year"] == year && actor.country == country;
});

當(dāng) controller 從 model 中取得想要的數(shù)據(jù)后,下一步就輪到 view 發(fā)光發(fā)熱了。view 層通常都會(huì)用到模板引擎,如 dust 等。同樣為了簡化,這里采用簡單替換模板中占位符的方式獲取 html,渲染得非常有限,粗略理解過程即可。

創(chuàng)建 /views/actors-list.js:

const actorTemplate = `
<h1>{name}</h1>
<p><em>Born: </em>{contry}, {year}</p>
<ul>{movies}</ul>
`;

exports.build = list => {
 let content = '';
 list.forEach(actor => {
 content += actorTemplate.replace('{name}', actor.name)
 .replace('{contry}', actor.country)
 .replace('{year}', actor["birth year"])
 .replace('{movies}', actor.movies.reduce((moviesHTML, movieName) => {
 return moviesHTML + `<li>${movieName}</li>`
 }, ''));
 });
 return content;
};

在瀏覽器中測試一下:

test mvc

至此,就大功告成啦!

以上這篇使用Node.js實(shí)現(xiàn)簡易MVC框架的方法就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

文檔

使用Node.js實(shí)現(xiàn)簡易MVC框架的方法

使用Node.js實(shí)現(xiàn)簡易MVC框架的方法:在使用Node.js搭建靜態(tài)資源服務(wù)器一文中我們完成了服務(wù)器對(duì)靜態(tài)資源請(qǐng)求的處理,但并未涉及動(dòng)態(tài)請(qǐng)求,目前還無法根據(jù)客戶端發(fā)出的不同請(qǐng)求而返回個(gè)性化的內(nèi)容。單靠靜態(tài)資源豈能撐得起這些復(fù)雜的網(wǎng)站應(yīng)用,本文將介紹如何使用Node處理動(dòng)態(tài)請(qǐng)求,以及如何搭建
推薦度:
標(biāo)簽: js 簡單的 node.js
  • 熱門焦點(diǎn)

最新推薦

猜你喜歡

熱門推薦

專題
Top
主站蜘蛛池模板: 国产a国产片 | 亚洲另类中文字幕 | 国产精品1区2区3区在线播放 | 亚洲自拍另类 | 国产成人无精品久久久 | 欧美日韩亚洲国产一区二区三区 | 国产在线精品观看一区 | 精品一区二区三区四区五区 | 国产成人精品一区二区三区… | 欧美精品v国产精品v日韩精品 | 国产成人+综合亚洲+天堂 | 日韩成人在线电影 | 97成人资源 | 精品一区二区三区3d动漫 | 在线播放一区 | 久草综合在线 | 在线视频一二三区2021不卡 | 欧美在线视频免费 | 久久99精品久久久久久噜噜 | 最新国产区 | 国产第一页在线视频 | 九一毛片| 欧美日韩1区 | 国产高清美女一级a毛片久久 | 免费国产高清视频 | 91麻精品国产91久久久久 | 在线欧美v日韩v国产精品v | 看全色黄大色大片免费久久 | 欧美高清视频在线 | 狠狠色狠狠色综合日日不卡 | 国产精品一区欧美日韩制服 | 国产日韩一区二区三区在线播放 | 亚洲欧洲久久 | 欧美综合自拍亚洲综合 | 精品一区二区三区五区六区七区 | 广东东莞一级毛片免费 | 日韩欧美亚州 | 亚洲一区二区三区四区在线 | 国产成人三级经典中文 | 中文字幕一区二区在线观看 | 黄色国产网站 |