# express
# 安装
使用脚手架 express-generator 来生成 express 框架。
npm install express-generator -g
express express-test
npm install & npm start
# 使用
app.js:
// 处理路由请求出错
var createError = require('http-errors');
var express = require('express');
var path = require('path');
const fs = require('fs')
// 解析cookie 可以通过 req.cookie 访问 cookie 内容
var cookieParser = require('cookie-parser');
// 记录日志的插件
var logger = require('morgan');
// 使用 express-session 处理session
const session = require('express-session')
// 将 session 存储到 redis 中
const RedisStore = require('connect-redis')(session)
// 引入两个路由
const blogRouter = require('./routes/blog')
const userRouter = require('./routes/user')
var app = express();
const ENV = process.env.NODE_ENV
// https://github.com/expressjs/morgan 查看打印日志的格式
if(ENV != 'production') {
// 对日志进行配置
app.use(logger('dev', {
// 标准输出,直接输出到控制台
stream: process.stdout
}));
} else {
// 线上环境使用,打印的日志比较详细--将日志写到access.log文件当中
const logFileName = path.join(__dirname, 'logs', 'access.log')
const writeStream = fs.createWriteStream(logFileName, {
flags: 'a'
})
app.use(logger('combined', {
stream: writeStream
}))
}
// 线上环境使用,打印的日志比较详细
// app.use(logger('combined', {
// stream: process.stdout
// }))
// 处理 post 请求的数据,获取 content-type=applica/json 格式的数据,可以通过 req.body 获取数据
app.use(express.json());
// 处理 post 数据,兼容处理其他数据
app.use(express.urlencoded({ extended: false }));
// 解析cookie
app.use(cookieParser());
const redisClient = require('./db/redis')
const sessionStore = new RedisStore({
client: redisClient
})
// 解析 session
app.use(session({
// 密匙
secret: 'jiegiser_95#',
cookie: {
// path: '/', // 模式配置
// httpOnly: true, // 默认配置
maxAge: 24 * 60 * 60 * 1000
},
// session 存储到 redis 中
store: sessionStore
}));
// public文件夹
// app.use(express.static(path.join(__dirname, 'public')));
// 注册路由
// app.use('/', indexRouter);
// 访问/users/
// app.use('/users', usersRouter);
app.use('/api/blog', blogRouter);
app.use('/api/user', userRouter);
// 检测 404
app.use(function(req, res, next) {
next(createError(404));
});
// 处理错误信息
app.use(function(err, req, res, next) {
// dev环境错误会直接抛出
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'dev' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
处理路由:
// get 请求
router.get('/login-test', (req, res, next) => {
// req.query 中存放 get 请求中的 queryString
if(req.session.username) {
// 可以直接返回 json 数据
res.json({
errno: 0,
msg: '测试成功'
})
return
}
res.json({
errno: -1,
msg: '测试失败'
})
})
// post 请求
router.post('/login', function(req, res, next) {
// express.json() 解析得到的数据存放在 req.body
const { username, password } = req.body
// 返回json格式的数据。相当于之前的res.end(JSON.stringify)
res.json({
errmo: 0,
data: {
username,
password
}
})
});
# 中间件
可以单独安装 express:npm install express;
next 函数执行下一个 app.use() 方法;
const express = require('express')
// 本次 http 请求的示例
const app = express()
app.use((req, res, next) => {
console.log('请求开始...', req.method, req.url)
next()
})
app.use((req, res, next) => {
// 处理 cookie
req.cookie = {
userId: 'jiegiser'
}
next()
})
app.use((req, res, next) => {
// 处理 post data
// 异步
setTimeout(() => {
req.body = {
a: 100,
b: 2100
}
next()
})
})
app.use('/api', (req, res, next) => {
console.log('处理 /api 路由')
next()
})
app.get('/api', (req, res, next) => {
console.log('get /api 路由')
next()
})
app.post('/api', (req, res, next) => {
console.log('post /api 路由')
next()
})
// 模拟登陆验证
// function loginCheck(req, res, next) {
// console.log('模拟登陆成功')
// setTimeout(() => {
// next()
// })
// }
// 登陆失败
function loginCheck(req, res, next) {
console.log('模拟登陆成功')
setTimeout(() => {
res.json({
errno: -1,
msg: '登陆失败'
})
// 不执行 next()
})
}
// 可以是多个函数 --可以是登陆验证,权限验证等等。前面的方法进行验证
app.get('/api/get-cookie', loginCheck, (req, res, next) => {
console.log('get /api/get-cookie')
res.json({
errno: 0,
data: req.cookie
})
})
app.post('/api/get-post-data', (req, res, next) => {
console.log('post apo/post/data')
res.json({
errno: 0,
data: req.body
})
})
app.use((req, res, next) => {
console.log('处理 404')
res.json({
errno: -1,
msg: '404 not found'
})
})
app.listen(5000, () => {
console.log('server is running on port 5000')
})
# express-session 处理 session
安装 express-session 插件:npm install express-session --save;他可以进行处理 session 相关:
// 使用 express-session 处理 session
const session = require('express-session')
// 设置 session
app.use(session({
// 密匙
secret: 'jiegiser_95#',
cookie: {
path: '/', // 模式配置
httpOnly: true, // 默认配置
maxAge: 24 * 60 * 60 * 1000 // 过期时间间隔 expires 是需要设置到期的具体日期;
}
}));
// 设置 用户 session
router.post('/login', function(req, res, next) {
const { username, password } = req.body
const result = login(username, password)
return result.then(data => {
if(data.username) {
req.session.username = data.username
req.session.realname = data.realname
res.json(new SuccessModel())
return
}
res.json(new ErrorModel('登录失败'))
})
});
// 读取 session
router.get('/login-test', (req, res, next) => {
if(req.session.username) {
res.json({
errno: 0,
msg: '测试成功'
})
return
}
res.json({
errno: -1,
msg: '测试失败'
})
})
# express-session 和 connect-redis 存储 session
安装 redis 以及 connect-redis 插件,npm i redis connect-redis --save;
进行配置 redis
const redis = require('redis')
const { REDIS_CONF } = require('../config/db')
// 创建客户端
const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host)
redisClient.on('error', err => {
console.error(err)
})
module.exports = redisClient
使用 connect-redis 进行存储 session:
// 使用 express-session 处理 session
const session = require('express-session')
// 将 session 存储到 redis 中
const RedisStore = require('connect-redis')(session)
// ./db/redis 是上面创建 redis 的配置文件
const redisClient = require('./db/redis')
const sessionStore = new RedisStore({
client: redisClient
})
// 解析session
app.use(session({
// 密匙
secret: 'jiegiser_95#',
cookie: {
// path: '/', // 模式配置
// httpOnly: true, // 默认配置
maxAge: 24 * 60 * 60 * 1000
},
// session存储到redis中
store: sessionStore
}));
从 redis 中读取 session:
const { ErrorModel } = require('../model/resModel')
// 写一个中间件
module.exports = (req, res, next) => {
if(req.session.username) {
// 已经登录了
next()
return
}
res.json(
new ErrorModel('未登录')
)
}
# 使用 morgan 记录日志
// 记录日志的插件
var logger = require('morgan');
const ENV = process.env.NODE_ENV
// https://github.com/expressjs/morgan 查看打印日志的格式
if(ENV != 'production') {
// 对日志进行配置 -下面的写法相当于 app.use(logger('dev'))
app.use(logger('dev', {
// 标准输出,直接输出到控制台
// 输出内容::method :url :status :response-time ms - :res[content-length]
stream: process.stdout
}));
} else {
// 线上环境使用,打印的日志比较详细--将日志写到 access.log 文件当中
const logFileName = path.join(__dirname, 'logs', 'access.log')
const writeStream = fs.createWriteStream(logFileName, {
flags: 'a'
})
app.use(logger('combined', {
// 输出内容::remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"
stream: writeStream
}))
}
express 与 node 之间的主要区别,就是他对我们的操作进行了很多封装:
- 写法上:如 req.query、res.json ,以及 router.get 跟 router.post
- 使用 express-session,connect-redis 等中间件更加方便
- 使用 morgan 操作日志
# 中间件原理
一个简单的中间件模式:使用 app.use 注册路由,app.get 、app.post 等方法
app.use((req, res, next) => {
console.log('请求开始...', req.method, req.url)
next()
})
// 使用多个中间件
// 可以是多个函数 --可以是登陆验证,权限验证等等。router 也就是这样的原理
app.get('/api/get-cookie', loginCheck, (req, res, next) => {
console.log('get /api/get-cookie')
res.json({
errno: 0,
data: req.cookie
})
})
分析:
- app.use 用来注册中间件,先收集
- 遇到 http 请求,根据 path 和 method 判断触发哪些方法
- 实现 next 机制,即上一个通过 next 触发下一个
- 可以通过调用 use() 函数来注册新的中间件,通常,新的中间件只能被添加到高压包带的末端,但不是严格要求这么做;
- 当接收到需要处理的新数据时,注册的中间件在意不执行流程中被依次调用。每个中间件都接受上一个中间件的执行结果作为输入值;
- 每个中间件都可以停止数据的进一步处理,只需要简单地不调用它的毁掉函数或者将错误传递给回调函数。当发生错误时,通常会触发执行另一个专门处理错误的中间件。
实现一个简单的 express:
const http = require('http')
const slice = Array.prototype.slice
class LikeExpress {
constructor() {
// 存放中间件的列表
this.routers = {
all: [], // app.use(...)
get: [], // app.get(...)
post: [] // app.post(...)
}
}
// 处理注册
register(path) {
const info = {}
if(typeof path === 'string') {
info.path = path
// 从第二个参数开始转换为数组,最终获取到的是一个数组
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.routers.all.push(info)
}
get() {
const info = this.register.apply(this, arguments)
this.routers.get.push(info)
}
post() {
const info = this.register.apply(this, arguments)
this.routers.post.push(info)
}
match(method, url) {
let stack = []
if(url === '/favicon.ico') {
return stack
}
// 获取 routes
let curRoutes = []
// 通过 use 注册的路由不管是 get 还是 post 请求都要执行
curRoutes = curRoutes.concat(this.routers.all)
// 然后根据请求的类型获取到对应的路由
curRoutes = curRoutes.concat(this.routers[method])
curRoutes.forEach(routerInfo => {
if (url.indexOf(routerInfo.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(routerInfo.stack)
}
})
return stack
}
// 核心的 next 机制
handle(req, res, stack) {
const next = () => {
// 拿到第一个匹配的中间件
const middleware = stack.shift()
if(middleware) {
console.log(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()
}
使用上面的代码:
const express = require('./like-express')
// 本次 http 请求的实例
const app = express()
console.log(app.use)
app.use((req, res, next) => {
console.log('请求开始', req.method, req.url)
next()
})
app.use((req, res, next) => {
// 假设在处理 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-cookoe')
res.json({
errno: 0,
data: req.cookie
})
})
app.listen(8888, () => {
console.log('server is running on port 8888')
})
阅读量: