Express 是一个自身功能极简,完全是由路由和中间件构成一个的 web 开发框架:从本质上来说,一个 Express 应用就是在调用各种中间件。
中间件(Middleware) 是一个函数,它可以访问请求对象(request object (req)), 响应对象(response object (res)),和 web 应用中处理请求-响应循环流程中的中间件,一般被命名为 next 的变量
了解中间件
简单讲:中间件 就是匹配路由之前和匹配路由之后做的一系列操作,如果没有next()这个中间件以后程序不会继续向下执行
中间件是怎么运转的
通过app.use/get/post的方法 参数是一个函数 就称为一个中间件 中间件的三个参数 req res next
中间件的功能包括:
- 1、执行任何代码。 2、修改请求和响应对象。3、终结请求-响应循环。 4、 调用堆栈中的下一个中间件。
实际应用
权限判断:没有登录 跳转到登录页面,登录以后就显示登录以后的页面
实际应用
resModel.js
class BaseModel {
constructor(data, message) {
if (typeof data === 'string') {
this.message = data
data = null
message = null
}
if (data) {
this.data = data
}
if (message) {
this.message = message
}
}
}
class SuccessModel extends BaseModel {
constructor(data, message) {
super(data, message)
this.errno = 0
}
}
class ErrorModel extends BaseModel {
constructor(data, message) {
super(data, message)
this.errno = -1
}
}
module.exports = {
SuccessModel,
ErrorModel
}
loginCheck.js
const { ErrorModel } = require('../model/resModel') ///登录中间件
module.exports = (req, res, next) => {
if (req.session.username) {
next()
return
}
res.json(
new ErrorModel('未登录')
)
}
routes/blogs.js
var express = require('express');
var router = express.Router();
const {
getList,
getDetail,
newBlog,
updateBlog,
delBlog
} = require('../controller/blog')
const { SuccessModel, ErrorModel } = require('../model/resModel')
const loginCheck = require('../middleware/loginCheck')
router.get('/list', (req, res, next) => {
let author = req.query.author || ''
const keyword = req.query.keyword || ''
if (req.query.isadmin) {
console.log('is admin')
// 管理员界面
if (req.session.username == null) {
console.error('is admin, but no login')
// 未登录
res.json(
new ErrorModel('未登录')
)
return
}
// 强制查询自己的博客
author = req.session.username
}
const result = getList(author, keyword)
return result.then(listData => {
res.json(
new SuccessModel(listData)
)
})
});
router.get('/detail', (req, res, next) => {
const result = getDetail(req.query.id)
return result.then(data => {
res.json(
new SuccessModel(data)
)
})
});
router.post('/new', loginCheck, (req, res, next) => {
req.body.author = req.session.username
const result = newBlog(req.body)
return result.then(data => {
res.json(
new SuccessModel(data)
)
})
})
router.post('/update', loginCheck, (req, res, next) => {
const result = updateBlog(req.query.id, req.body)
return result.then(val => {
if (val) {
res.json(
new SuccessModel()
)
} else {
res.json(
new ErrorModel('更新博客失败')
)
}
})
})
router.post('/del', loginCheck, (req, res, next) => {
const author = req.session.username
const result = delBlog(req.query.id, author)
return result.then(val => {
if (val) {
res.json(
new SuccessModel()
)
} else {
res.json(
new ErrorModel('删除博客失败')
)
}
})
})
module.exports = router;
原理及实现
面试选考题
1、用app.use/get/post来注册中间件。2、遇到http请求根据path、method判断是否触发中间。3、实现next机制上一个通过next触发下一个,本质是递归函数
./like-express
const http = require('http')
const slice = Array.prototype.slice
// 中间件 实现原理
class LikeExpress {
constructor() {
// 存放中间件的列表
this.routes = {
all: [], // app.use(...)
get: [], // app.get(...)
post: [] // app.post(...)
}
}
// 注册中间件的通用方法
register(path) {
const info = {}
if (typeof path === 'string') {
info.path = path
// 从第二个参数开始,转换为数组,存入 stack
info.stack = slice.call(arguments, 1)
} else {
info.path = '/'
// 从第一个参数开始,转换为数组,存入 stack
info.stack = slice.call(arguments, 0)
}
return info
}
use() {
const info = this.register.apply(this, arguments)
this.routes.all.push(info)
}
get() {
const info = this.register.apply(this, arguments)
this.routes.get.push(info)
}
post() {
const info = this.register.apply(this, arguments)
this.routes.post.push(info)
}
match(method, url) {
let stack = []
if (url === '/favicon.ico') {
return stack
}
// 获取 routes
let curRoutes = []
curRoutes = curRoutes.concat(this.routes.all)
curRoutes = curRoutes.concat(this.routes[method])
curRoutes.forEach(routeInfo => {
if (url.indexOf(routeInfo.path) === 0) {
// url === '/api/get-cookie' 且 routeInfo.path === '/'
// url === '/api/get-cookie' 且 routeInfo.path === '/api'
// url === '/api/get-cookie' 且 routeInfo.path === '/api/get-cookie'
stack = stack.concat(routeInfo.stack)
}
})
return stack
}
// 核心的 next 机制
handle(req, res, stack) {
const next = () => {
// 拿到第一个匹配的中间件
const middleware = stack.shift() // 拿到数组的第一个
if (middleware) {
// 执行中间件函数
middleware(req, res, next)
}
}
next()
}
callback() {
return (req, res) => {
res.json = (data) => {
res.setHeader('Content-type', 'application/json')
res.end(
JSON.stringify(data)
)
}
const url = req.url
const method = req.method.toLowerCase()
const resultList = this.match(method, url)
this.handle(req, res, resultList)
}
}
listen(...args) {
const server = http.createServer(this.callback())
server.listen(...args)
}
}
// 工厂函数
module.exports = () => {
return new LikeExpress()
}
test.js
const express = require('./like-express')
// 本次 http 请求的实例
const app = express()
app.use((req, res, next) => {
console.log('请求开始...', req.method, req.url)
console.log(['23','2322'].shift());
next()
})
app.use((req, res, next) => {
// 假设在处理 cookie
console.log('处理 cookie ...')
req.cookie = {
userId: 'abc123'
}
next()
})
app.use('/api', (req, res, next) => {
console.log('处理 /api 路由')
next()
})
app.get('/api', (req, res, next) => {
console.log('get /api 路由')
next()
})
// 模拟登录验证
function loginCheck(req, res, next) {
setTimeout(() => {
console.log('模拟登陆成功')
next()
})
}
app.get('/api/get-cookie', loginCheck, (req, res, next) => {
console.log('get /api/get-cookie')
res.json({
errno: 0,
data: req.cookie
})
})
app.listen(8000, () => {
console.log('server is running on port 8000')
})
测试
浏览器打开:http://localhost:8000/api/get-cookie
终端控制台打印
server is running on port 8000
请求开始… GET /api/get-cookie
23
处理 cookie …
处理 /api 路由
get /api 路由
模拟登陆成功
get /api/get-cookie
基础扩展了解
实现类数组转数组
const slice = Array.prototype.slice
info.stack = slice.call(arguments, 1)
const middleware = stack.shift() // 拿到数组的第一个
//fn.apply(this,array)。 这里的this指向执行fn函数的作用域
const info = this.register.apply(this, arguments)