L'éditeur suivant vous proposera un article sur la façon d'implémenter un framework MVC simple à l'aide de Node.js. L'éditeur le trouve plutôt bon, je vais donc le partager avec vous maintenant et le donner comme référence pour tout le monde. Suivons l'éditeur et jetons un coup d'œil.
Dans l'article Construire un serveur de ressources statiques à l'aide de Node.js, nous avons terminé le traitement par le serveur des demandes de ressources statiques, mais n'impliquons pas de demandes dynamiques. Actuellement, cela ne peut pas être le cas. émis en fonction du client et renvoie un contenu personnalisé pour différentes demandes. Les ressources statiques ne peuvent pas à elles seules prendre en charge ces applications de sites Web complexes. Cet article explique comment utiliser <span style="font-family:NSimsun">Node</span>
pour gérer les requêtes dynamiques et comment créer un framework MVC simple. Étant donné que l'article précédent a présenté en détail comment répondre aux demandes de ressources statiques, cet article ignorera toutes les parties statiques.
Un exemple simple
Commencez par un exemple simple pour comprendre comment renvoyer du contenu dynamique au client dans Node.
Supposons que nous ayons une telle exigence :
Renvoyer les acteurs lorsque l'utilisateur accède à la liste <span style="font-family:NSimsun">/actors<code><span style="font-family:NSimsun">/actors</span>
page
Lorsque l'utilisateur visite <span style="font-family:NSimsun">/actresses<code><span style="font-family:NSimsun">/actresses</span>
pour renvoyer la liste des actrices
peut utiliser la Fonction de complétion de code suivante :
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(Un exemple simple danalyse de la façon dutiliser Node.js pour implémenter le framework MVC); res.end('<h1>Requested page not found.</h1>') } }).listen(9527);
Le cœur du code ci-dessus est la correspondance de route. Lorsqu'une requête arrive, vérifiez s'il existe un traitement logique correspondant à son chemin. la demande ne correspond pas, pour n'importe quel itinéraire, Un exemple simple danalyse de la façon dutiliser Node.js pour implémenter le framework MVC est renvoyé. La logique correspondante est traitée lorsque la correspondance est réussie.
Le code ci-dessus n'est évidemment pas universel, et il n'y a que deux candidats correspondant à l'itinéraire (et la méthode de requête n'a pas été distinguée), et la base de données et les fichiers modèles n'ont pas été utilisé. Sous le principe, le code est déjà un peu emmêlé. Nous allons donc ensuite créer un framework MVC simple pour séparer les données, les modèles et les performances, et chacun pourra effectuer ses propres tâches.
Construire un framework MVC simple
MVC fait respectivement référence à :
M : Modèle (données)
V : Vue (performance)
C : Contrôleur (logique)
Dans Node, le processus de traitement des requêtes sous l'architecture MVC est le suivant :
Arrivée de la demande Le serveur
Le serveur transmet la demande au routage
Le routage dirige la demande vers le contrôleur correspondant via la correspondance de chemin
Le contrôleur reçoit le demande et demande au modèle Données
le modèle renvoie les données requises au contrôleur
le contrôleur devra peut-être retraiter les données reçues
le contrôleur remet les données traitées pour visualiser
la vue génère un contenu de réponse basé sur des données et des modèles
Le serveur renvoie ce contenu au client
Sur cette base, nous devons préparer les modules suivants :
serveur : surveiller et répondre aux requêtes
routeur : gérer la requête au bon contrôleur pour le traitement
contrôleurs : exécuter la logique métier, récupérer les données du modèle et les transmettre à la vue
modèle : Fournir des données
vue : Fournir du html
Créer le répertoire suivant :
-- server.js -- lib -- router.js -- views -- controllers -- models
serveur
Créer le fichier 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`); } });
Quels que soient les détails de ce fichier, le routeur est le module qui sera complété ci-dessous. Il sera d'abord introduit ici, et une fois la demande arrivée, laissez-le s'en occuper.
Module routeur
Le module routeur n'a en fait besoin que d'effectuer une seule chose, diriger la demande vers le contrôleur approprié pour le traitement. Idéalement, il peut être utilisé comme ceci :
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(); });
En général, nous espérons qu'il prend en charge à la fois le middleware de routage et le non-middleware. Une fois la demande arrivée, le routeur la remettra au middleware correspondant. traitement. Le middleware est une fonction qui peut accéder aux objets de requête et aux objets de réponse. Les choses qui peuvent être faites dans le middleware incluent :
Exécuter n'importe quel code, comme l'ajout de journaux et la gestion des erreurs, etc.
Modifier le request ( req) et l'objet de réponse (res), par exemple, récupérez les paramètres de requête de req.url et attribuez-les à req.query
Fin de réponse
Appel le middleware suivant (suivant)
Remarque :
Il convient de noter que si la réponse n'est ni terminée ni si la méthode suivante n'est appelée dans un middleware, le contrôle est confié au middleware suivant, la requête sera suspendue.
__Le middleware sans routage__ est ajouté de la manière suivante, correspondant à toutes les requêtes :
router.use(fn);
Par exemple, dans l'exemple ci-dessus :
router.use((req, res, next) => { console.info('New request arrived'); next() });
__routing middleware__ est ajouté de la manière suivante pour correspondre exactement à la méthode et au chemin de la requête :
router.HTTP_METHOD(path, fn)
Après le tri, écrivez d'abord le framework :
/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 }); }; }); };
Ce qui précède ajoute principalement des méthodes use, get, post et autres au routeur Chaque fois que ces méthodes sont appelées, une règle de route est ajoutée aux routes.
Remarque :
Une fonction en Javascript est un objet spécial qui peut être appelé et qui possède également des propriétés et des méthodes.
L'accent suivant est mis sur la fonction du routeur. Ce qu'elle doit faire est :
Obtenir la méthode et le chemin
à partir de l'objet req.依据 method、pathname 将请求与routes数组内各个 route 按它们被添加的顺序依次匹配
如果与某个route匹配成功,执行 route.handler,执行完后与下一个 route 匹配或结束流程 (后面详述)
如果匹配不成功,继续与下一个 route 匹配,重复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(); };
对于非路由中间件,直接调用其 handler。对于路由中间件,只有请求方法和路径都匹配成功时,才调用其 handler。当没有匹配上的 route 时,直接与下一个route继续匹配。
需要注意的是,在某条 route 匹配成功的情况下,执行完其 handler 之后,还会不会再接着与下个 route 匹配,就要看开发者在其 handler 内有没有主动调用 next() 交出控制权了。
在__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 = Un exemple simple danalyse de la façon dutiliser Node.js pour implémenter le framework MVC; res.end(); });
每个请求抵达时,首先打印出一条 log,接着匹配其他route。当匹配上 actors 或 actresses 的 get 请求时,直接发回演员名字,并不需要继续匹配其他 route。如果都没匹配上,返回 Un exemple simple danalyse de la façon dutiliser Node.js pour implémenter le framework MVC。
在浏览器中依次访问 http://localhost:9527/erwe、http://localhost:9527/actors、http://localhost:9527/actresses 测试一下:
<span style="font-family:NSimsun">network</span>
中观察到的结果符合预期,同时后台命令行中也打印出了三条 <span style="font-family:NSimsun">New request arrived</span>
语句。
接下来继续改进 router 模块。
首先添加一个 router.all 方法,调用它即意味着为所有请求方法都添加了一条 route:
router.all = (path, fn) => { METHODS.forEach(item => { const method = item.toLowerCase(); router[method](path, fn); }) };
接着,添加错误处理。
/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); }); ...
默认情况下,遇到错误时会返回 500,但开发者使用 router 模块时可以传入自己的错误处理函数将其替代。
修改一下代码,测试是否能正确执行错误处理:
router.use((req, res, next) => { console.info('New request arrived'); next(new Error('an error')); });
这样任何请求都应该返回 500:
继续,修改 route.path 与 pathname 的匹配规则。现在我们认为只有当两字符串相等时才让匹配通过,这没有考虑到 url 中包含路径参数的情况,比如:
localhost:9527/actors/Leonardo
与
router.get('/actors/:name', someRouteHandler);
这条route应该匹配成功才是。
新增一个函数用来将字符串类型的 route.path 转换成正则对象,并存入 route.pattern:
const getRoutePattern = pathname => { pathname = '^' + pathname.replace(/(\:\w+)/g, '\(\[a-zA-Z0-9-\]\+\\s\)') + '$'; return new RegExp(pathname); };
这样就可以匹配上带有路径参数的url了,并将这些路径参数存入 req.params 对象:
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]; } } }
添加个 route 测试一下:
router.get('/actors/:year/:country', (req, res) => { res.end(`year: ${req.params.year} country: ${req.params.country}`); });
访问<span style="font-family:NSimsun">http://localhost:9527/actors/1990/China</span>
试试:
router 模块就写到此,至于查询参数的格式化以及获取请求主体,比较琐碎就不试验了,需要可以直接使用 bordy-parser 等模块。
现在我们已经创建好了router模块,接下来将 route handler 内的业务逻辑都转移到 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 中同时引入了 view 和 model, 其充当了这二者间的粘合剂。回顾下 controller 的任务:
controller 收到请求,向 model 索要数据
model 给 controller 返回其所需数据
controller 可能需要对收到的数据做一些再加工
controller 将处理好的数据交给 view
在此 controller 中,我们将调用 model 模块的方法获取演员列表,接着将数据交给 view,交由 view 生成呈现出演员列表页的 html 字符串。最后将此字符串返回给客户端,在浏览器中呈现列表。
从 model 中获取数据
通常 model 是需要跟数据库交互来获取数据的,这里我们就简化一下,将数据存放在一个 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 中定义一些方法来访问这些数据。
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; });
当 controller 从 model 中取得想要的数据后,下一步就轮到 view 发光发热了。view 层通常都会用到模板引擎,如 dust 等。同样为了简化,这里采用简单替换模板中占位符的方式获取 html,渲染得非常有限,粗略理解过程即可。
创建 /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; };
在浏览器中测试一下:
至此,就大功告成啦!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!