共计 4730 个字符,预计需要花费 12 分钟才能阅读完成。
对于中间件
狭义的中间件指操作系统以外可能为利用提供性能的软件,大到云计算厂商的各类服务,小到某一个字段的检测模块。Express 中间件特指基于 Express 中间件机制提供性能的软件模块。Express 在 3.x 及以前的版本依赖 Connect 作为底层提供中间件机制,从 4.x 版本开始内置了与 Connect 兼容的中间件机制,因而基于 Connect 的中间件都能间接在 Express 中应用。
Express 会将处理过程以队列的形式进行组织,在分派申请时,通过递归传递 next 办法顺次调用处理过程(详见源码):
写一个路由修改中间件
在上一章已实现的工程 host1-tech/nodejs-server-examples – 02-validate 有一个小小的问题,无法访问门路不标准的接口,比方无法访问 http://localhost:9000/api//shop:
当初通过中间件来解决此类问题:
$ mkdir src/middlewares # 新建 src/middlewares 目录寄存自定义中间件 | |
$ tree -L 2 -I node_modules # 展现除了 node_modules 之外的目录内容构造 | |
. | |
├── Dockerfile | |
├── package.json | |
├── public | |
│ ├── glue.js | |
│ ├── index.css | |
│ ├── index.html | |
│ └── index.js | |
├── src | |
│ ├── controllers | |
│ ├── middlewares | |
│ ├── moulds | |
│ ├── server.js | |
│ └── services | |
└── yarn.lock |
// src/middlewares/urlnormalize.js | |
const {normalize} = require('path'); | |
const {parse, format} = require('url'); | |
module.exports = function urlnormalizeMiddleware() {return (req, res, next) => {const pathname = normalize(req.path); | |
const urlParsed = parse(req.url); | |
let shouldRedirect = false; | |
// 重定向不标准的门路 | |
if (req.path != pathname) { | |
urlParsed.pathname = pathname; | |
shouldRedirect = true; | |
} | |
// 执行重定向或者略过 | |
if (shouldRedirect) {res.redirect(format(urlParsed)); | |
} else {next(); | |
} | |
}; | |
}; |
// src/middlewares/index.js | |
const {Router} = require('express'); | |
const urlnormalizeMiddleware = require('./urlnormalize'); | |
module.exports = async function initMiddlewares() {const router = Router(); | |
router.use(urlnormalizeMiddleware()); | |
return router; | |
}; |
// src/server.js | |
const express = require('express'); | |
const {resolve} = require('path'); | |
const {promisify} = require('util'); | |
+const initMiddlewares = require('./middlewares'); | |
const initControllers = require('./controllers'); | |
// ... | |
async function bootstrap() {server.use(express.static(publicDir)); | |
server.use('/moulds', express.static(mouldsDir)); | |
+ server.use(await initMiddlewares()); | |
server.use(await initControllers()); | |
await promisify(server.listen.bind(server, port))(); | |
console.log(`> Started on port ${port}`); | |
} | |
bootstrap(); |
拜访 http://localhost:9000/api//shop 即可看到主动重定向至无效路由:
补充店铺新增逻辑
到目前为止的店铺治理短少了店铺新增逻辑,因为 post 解析须要依赖 body-parser 这一中间件,所以才在本章补充这一性能。执行 body-parser 装置命令:
$ yarn add body-parser | |
# ... | |
info Direct dependencies | |
└─ body-parser@1.19.0 | |
# ... |
后端解决:
// src/services/shop.js | |
// ... | |
class ShopService { | |
// ... | |
+ async create({values}) {+ await delay(); | |
+ | |
+ const id = String( | |
+ 1 + | |
+ Object.keys(memoryStorage).reduce((m, id) => Math.max(m, id), -Infinity) | |
+ ); | |
+ | |
+ return {id, ...(memoryStorage[id] = values) }; | |
+ } | |
} | |
// ... |
// src/controllers/shop.js | |
const {Router} = require('express'); | |
+const bodyParser = require('body-parser'); | |
const shopService = require('../services/shop'); | |
const {createShopFormSchema} = require('../moulds/ShopForm'); | |
class ShopController { | |
shopService; | |
async init() {this.shopService = await shopService(); | |
const router = Router(); | |
router.get('/', this.getAll); | |
router.get('/:shopId', this.getOne); | |
router.put('/:shopId', this.put); | |
router.delete('/:shopId', this.delete); | |
+ router.post('/', bodyParser.urlencoded({ extended: false}), this.post); | |
return router; | |
} | |
// ... | |
+ post = async (req, res) => {+ const { name} = req.body; | |
+ | |
+ try {+ await createShopFormSchema().validate({name}); | |
+ } catch (e) {+ res.status(400).send({success: false, message: e.message}); | |
+ return; | |
+ } | |
+ | |
+ const shopInfo = await this.shopService.create({values: { name} }); | |
+ | |
+ res.send({success: true, data: shopInfo}); | |
+ }; | |
} | |
// ... |
前端解决:
// public/index.js | |
// ... | |
export async function refreshShopList() {const res = await fetch('/api/shop'); | |
const {data: shopList} = await res.json(); | |
const htmlItems = shopList.map(({ id, name}) => ` | |
<li data-shop-id="${id}"> | |
<div data-type="text">${name}</div> | |
<input type="text" placeholder="输出新的店铺名称" /> | |
<a href="#" data-type="modify"> 确认批改 </a> | |
<a href="#" data-type="remove"> 删除店铺 </a> | |
<div class="error"></div> | |
</li>` | |
); | |
document.querySelector('#root').innerHTML = ` | |
<h1> 店铺列表:</h1> | |
-<ul class="shop-list">${htmlItems.join('')}</ul>`; | |
+<ul class="shop-list">${htmlItems.join('')}</ul> | |
+<h1> 店铺新增:</h1> | |
+<form method="post" action="/api/shop"> | |
+ <label> 新店铺的名称:</label> | |
+ <input type="text" name="name" /> | |
+ <button type="submit" data-type="create"> 确认新增 </button> | |
+ <span class="error"></span> | |
+</form>`; | |
} | |
export async function bindShopInfoEvents() {document.querySelector('#root').addEventListener('click', async (e) => {e.preventDefault(); | |
switch (e.target.dataset.type) { | |
case 'modify': | |
await modifyShopInfo(e); | |
break; | |
case 'remove': | |
await removeShopInfo(e); | |
break; | |
+ case 'create': | |
+ await createShopInfo(e); | |
+ break; | |
} | |
}); | |
} | |
// ... | |
+export async function createShopInfo(e) {+ e.preventDefault(); | |
+ const name = e.target.parentElement.querySelector('input[name=name]').value; | |
+ | |
+ try {+ await createShopFormSchema().validate({name}); | |
+ } catch ({message}) {+ e.target.parentElement.querySelector('.error').innerHTML = message; | |
+ return; | |
+ } | |
+ | |
+ await fetch('/api/shop', { | |
+ method: 'POST', | |
+ headers: { | |
+ 'Content-Type': 'application/x-www-form-urlencoded', | |
+ }, | |
+ body: `name=${encodeURIComponent(name)}`, | |
+ }); | |
+ | |
+ await refreshShopList(); | |
+} |
看一下店铺新增的成果:
本章源码
host1-tech/nodejs-server-examples – 03-middleware
更多浏览
从零搭建 Node.js 企业级 Web 服务器(零):动态服务
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
从零搭建 Node.js 企业级 Web 服务器(二):校验
从零搭建 Node.js 企业级 Web 服务器(三):中间件