Der folgende Editor bringt Ihnen einen Artikel darüber, wie Sie ein einfaches MVC-Framework mit Node.js implementieren. Der Herausgeber findet es ziemlich gut, deshalb werde ich es jetzt mit Ihnen teilen und es allen als Referenz geben. Folgen wir dem Editor und werfen wir einen Blick darauf.
Im Artikel Erstellen eines statischen Ressourcenservers mit Node.js haben wir die Verarbeitung statischer Ressourcenanforderungen durch den Server abgeschlossen, jedoch keine dynamischen Anforderungen. Derzeit ist dies nicht möglich basierend auf dem Kunden ausgegeben und personalisierte Inhalte für verschiedene Anfragen zurückgeben. Statische Ressourcen allein können diese komplexen Website-Anwendungen nicht unterstützen. In diesem Artikel wird erläutert, wie Sie mit <span style="font-family:NSimsun">Node</span>
dynamische Anforderungen verarbeiten und ein einfaches MVC-Framework erstellen. Da im vorherigen Artikel ausführlich beschrieben wurde, wie auf statische Ressourcenanforderungen reagiert werden soll, werden in diesem Artikel alle statischen Teile übersprungen.
Ein einfaches Beispiel
Beginnen Sie mit einem einfachen Beispiel, um zu verstehen, wie dynamische Inhalte an den Client in Node zurückgegeben werden.
Angenommen, wir haben eine solche Anforderung:
Gibt Schauspieler zurück, wenn der Benutzer auf die Liste <span style="font-family:NSimsun">/actors<code><span style="font-family:NSimsun">/actors</span>
zugreift Seite
Wenn der Benutzer <span style="font-family:NSimsun">/actresses<code><span style="font-family:NSimsun">/actresses</span>
besucht, um zur Liste der Schauspielerinnen zurückzukehren
kann das verwenden Folgende Code-Vervollständigungsfunktion:
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(Eine einfache Beispielanalyse zur Verwendung von Node.js zur Implementierung des MVC-Frameworks); res.end('<h1>Requested page not found.</h1>') } }).listen(9527);
Der Kern des obigen Codes ist der Routenabgleich. Überprüfen Sie, ob eine logische Verarbeitung vorhanden ist, die ihrem Pfad entspricht Die Anfrage stimmt nicht überein. Für jede Route wird Eine einfache Beispielanalyse zur Verwendung von Node.js zur Implementierung des MVC-Frameworks zurückgegeben. Bei erfolgreichem Match wird die entsprechende Logik abgearbeitet.
Der obige Code ist offensichtlich nicht universell und es gibt nur zwei Routenanpassungskandidaten (und die Anforderungsmethode wurde nicht unterschieden), die Datenbank und die Vorlagendateien jedoch nicht Unter der Prämisse ist der Code bereits etwas verworren. Als nächstes erstellen wir ein einfaches MVC-Framework, um Daten, Modelle und Leistung zu trennen, und jedes kann seine eigenen Aufgaben ausführen.
Erstellen Sie ein einfaches MVC-Framework
MVC bezieht sich jeweils auf:
M: Modell (Daten)
V: Ansicht (Leistung)
C: Controller (Logik)
In Node ist der Prozess der Verarbeitung von Anforderungen unter der MVC-Architektur wie folgt:
Anfrageeingang Der Server
Der Server übergibt die Anfrage an das Routing
Das Routing passt den Pfad an und leitet die Anfrage an den entsprechenden Controller weiter
Der Controller empfängt die Anfrage und bittet das Modell um die Daten
Modell gibt die erforderlichen Daten an den Controller zurück
Controller muss möglicherweise eine Neuverarbeitung der empfangenen Daten durchführen
Controller übergibt die verarbeiteten Daten Daten zum Anzeigen
Ansicht generiert Antwortinhalte basierend auf Daten und Vorlagen
Der Server gibt diesen Inhalt an den Client zurück
Auf dieser Grundlage müssen wir die folgenden Module vorbereiten:
Server: Anfragen überwachen und beantworten
Router: Weiterleiten der Anfrage an den richtigen Controller zur Verarbeitung
Controller: Geschäftslogik ausführen, Daten aus dem Modell abrufen und übergeben es der Ansicht hinzufügen
Modell: Daten bereitstellen
Ansicht: HTML bereitstellen
Erstellen Sie das folgende Verzeichnis:
-- server.js -- lib -- router.js -- views -- controllers -- models
Server
Server.js-Datei erstellen:
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`); } });
Unabhängig von den Details in dieser Datei ist der Router Das Modul, das unten vervollständigt wird, wird hier zuerst vorgestellt und nach Eintreffen der Anfrage bearbeitet.
Router-Modul
Das Router-Modul muss eigentlich nur eines erledigen, nämlich die Anfrage zur Verarbeitung an den richtigen Controller weiterleiten.
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(); });
Im Allgemeinen hoffen wir, dass es sowohl Routing-Middleware als auch Nicht-Middleware unterstützt. Nachdem die Anfrage eingegangen ist, übergibt der Router sie an die entsprechende Middleware Verarbeitung. Middleware ist eine Funktion, die auf Anforderungsobjekte und Antwortobjekte zugreifen kann:
Beliebigen Code ausführen, z. B. Protokolle hinzufügen und Fehler behandeln usw.
Ändern Anfrage ( req) und Antwortobjekt (res) rufen beispielsweise die Abfrageparameter von req.url ab und weisen sie req.query zu
Antwort beenden
Aufruf die nächste Middleware (next )
Hinweis:
Es ist zu beachten, dass die Kontrolle übergeben wird, wenn die Antwort weder beendet wird noch die nächste Methode in einer Middleware aufgerufen wird Weiter zur nächsten Middleware, die Anfrage bleibt hängen
__Nicht-Routing-Middleware__ wird auf folgende Weise hinzugefügt und passt zu allen Anfragen:
router.use(fn);
Zum Beispiel im obigen Beispiel:
router.use((req, res, next) => { console.info('New request arrived'); next() });
__routing middleware__ wird auf folgende Weise hinzugefügt, um der Anforderungsmethode und dem Pfad genau zu entsprechen:
router.HTTP_METHOD(path, fn)
Nachdem Sie es geklärt haben, schreiben Sie zuerst das 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 }); }; }); };
Das Obige fügt dem Router hauptsächlich Use-, Get-, Post- und andere Methoden hinzu. Immer wenn diese Methoden aufgerufen werden, wird den Routen eine Routenregel hinzugefügt.
Hinweis:
Eine Funktion in Javascript ist ein spezielles Objekt, das aufgerufen werden kann und auch Eigenschaften und Methoden haben kann.
Der nächste Fokus liegt auf der Router-Funktion. Was sie tun muss, ist:
Methode und Pfadnamen abrufen
aus dem req-Objekt依据 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 = Eine einfache Beispielanalyse zur Verwendung von Node.js zur Implementierung des MVC-Frameworks; res.end(); });
每个请求抵达时,首先打印出一条 log,接着匹配其他route。当匹配上 actors 或 actresses 的 get 请求时,直接发回演员名字,并不需要继续匹配其他 route。如果都没匹配上,返回 Eine einfache Beispielanalyse zur Verwendung von Node.js zur Implementierung des MVC-Frameworks。
在浏览器中依次访问 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; };
在浏览器中测试一下:
至此,就大功告成啦!
Das obige ist der detaillierte Inhalt vonEine einfache Beispielanalyse zur Verwendung von Node.js zur Implementierung des MVC-Frameworks. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!