基于Nodejs的Web服务端框架Express
|字数总计:3.6k|阅读时长:15分钟|阅读量:
Express
官网传送门(opens new window)
基于 Node.js 平台,快速、开放、极简的 Web 开发框架
Express 是用于快速创建服务器的第三方模块。
Express 初体验
基本使用
创建服务器,监听客户端请求,并返回内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const express = require('express')
const app = express()
app.get('/user', (req, res) => { res.send({ name: 'zs', age: 20, gender: '男' }) }) app.post('/user', (req, res) => { res.send('请求成功') })
app.get('/', (req, res) => { console.log(req.query) res.send(req.query) })
app.get('/user/:id/:username', (req, res) => { console.log(req.params) res.send(req.params) })
app.listen(80, () => { console.log('express server running at http://127.0.0.1') })
|
托管静态资源
- 通过
express.static()
方法可创建静态资源服务器,向外开放访问静态资源。
1 2 3 4 5 6 7 8 9 10 11
| app.use(express.static('public')) app.use(express.static('files')) app.use('/public', express.static('public'))
|
Express 路由
创建路由模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
const express = require('express')
const router = express.Router()
router.get('/user/list', (req, res) => { res.send('Get user list.') }) router.post('/user/add', (req, res) => { res.send('Add new user.') })
module.exports = router
|
注册路由模块:
1 2 3 4 5 6 7 8 9 10
| const express = require('express') const router = require('./router') const app = express()
app.use('/api', router)
app.listen(80, () => { console.log('http://127.0.0.1') })
|
Express 中间件
- 中间件是指流程的中间处理环节
- 服务器收到请求后,可先调用中间件进行预处理
- 中间件是一个函数,包含
req, res, next
三个参数,next()
参数把流转关系交给下一个中间件或路由
注意:
- 在注册路由之前注册中间件(错误级别中间件除外)
- 中间件可连续调用多个
- 别忘记调用
next()
函数
next()
函数后别写代码
- 多个中间件共享
req
、 res
对象
全局中间件
- 通过
app.use()
定义的中间件为全局中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const express = require('express') const app = express()
app.use((req, res, next) => { console.log('调用了第1个全局中间件') next() })
app.use((req, res, next) => { console.log('调用了第2个全局中间件') next() })
app.get('/user', (req, res) => { res.send('User page.') })
app.listen(80, () => { console.log('http://127.0.0.1') })
|
局部中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const express = require('express') const app = express()
const mw1 = (req, res, next) => { console.log('调用了第一个局部生效的中间件') next() }
const mw2 = (req, res, next) => { console.log('调用了第二个局部生效的中间件') next() }
app.get('/hello', mw2, mw1, (req, res) => res.send('hello page.')) app.get('/about', [mw1, mw2], (req, res) => res.send('about page.'))
app.get('/user', (req, res) => res.send('User page.'))
app.listen(80, function () { console.log('Express server running at http://127.0.0.1') })
|
中间件分类
应用级别的中间件
- 通过
app.use()
或 app.get()
或 app.post()
,绑定到 app
实例上的中间件
路由级别的中间件
- 绑定到
express.Router()
实例上的中间件,叫做路由级别的中间件。用法和应用级别中间件没有区别。应用级别中间件是绑定到 app
实例上,路由级别中间件绑定到 router
实例上。
1 2 3 4 5 6 7 8 9
| const app = express() const router = express.Router()
router.use(function (req, res, next) { console.log(1) next() })
app.use('/', router)
|
- 错误级别的中间件
- 用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
- 错误级别中间件的处理函数中,必须有 4 个形参,形参顺序从前到后分别是
(err, req, res, next)
- 错误级别的中间件必须注册在所有路由之后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const express = require('express') const app = express()
app.get('/', (req, res) => { throw new Error('服务器内部发生了错误!') res.send('Home page.') })
app.use((err, req, res, next) => { console.log('发生了错误!' + err.message) res.send('Error:' + err.message) })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1') })
|
- Express 内置中间件
自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:
express.static
快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
express.json
解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
express.urlencoded
解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
1 2 3
| app.use(express.static(__dirname, './public')) app.use(express.json()) app.use(express.urlencoded({ extended: false }))
|
- 第三方中间件(cors等)
CORS 跨域资源共享
安装配置
1 2 3
| npm install cors const cord = require('cors') app.use(cors())
|
- CORS(Cross-Origin Resource Sharing,跨域资源共享)解决跨域,浏览器是通过 HTTP 响应头决定是否阻止客户端跨域获取资源
- 浏览器的同源安全策略默认会阻止网页 “跨域” 获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头,就可解除浏览器端的跨域访问限制
- CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。
- CORS 在浏览器中有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。
CORS 常见响应头
Access-Control-Allow-Origin:制定了允许访问资源的外域源
1 2
| res.setHeader('Access-Control-Allow-Origin', 'http://example.com') res.setHeader('Access-Control-Allow-Origin', '*')
|
Access-Control-Allow-Headers:表示此次请求中允许哪些 header 字段
- 默认情况下,CORS 仅支持客户端向服务器发送如下的 9 个请求头:
Accept
、Accept-Language
、Content-Language
、DPR
、Downlink
、Save-Data
、Viewport-Width
、Width
、Content-Type
(Content-Type 的仅限于 text/plain
、multipart/form-data
、application/x-www-form-urlencoded
三者之一)
- 如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 A
ccess-Control-Allow-Headers
对额外的请求头进行声明,否则这次请求会失败!
1
| res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header')
|
Access-Control-Allow-Methods:表示此次请求中允许使用的请求方法
- 默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过
Access-Control-Alow-Methods
来指明实际请求所允许使用的 HTTP 方法
1 2
| res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD') res.setHEader('Access-Control-Allow-Methods', '*')
|
CORS 请求分类
简单请求
- 请求方式:GET、POST、HEAD 三者之一
- HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type(只有三个值 application/x-www-formurlencoded、multipart/form-data、text/plain)
预检请求
- 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
- 请求头中包含自定义头部字段
- 向服务器发送了 application/json 格式的数据
在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据
Express 中配置 Mysql
创建数据库连接池 db/index.js
1 2 3 4 5 6 7 8 9 10 11 12
| const mysql = require('mysql')
const db = mysql.createPool({ host: '127.0.0.1', user: 'root', password: '123456', database: 'forum', })
module.exports = db
|
操作 mysql 数据库 router_handler/user.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| const db = require('../db/index')
const selectSql = 'select * from users' db.query(selectSql, (err, results) => { if (err) return console.log(err.message) console.log(results) })
const user = { username: 'de', password: '123456'} const insertSql = 'insert into users (username, password) values(?, ?)' db.query(insertSql, [user.username, user.password], (err, result)=>{ if (err) return console.log(err.message) if(result.affectedRows === 1) { console.log('插入成功') } })
const user = { username: 'de', password: '123456'} const insertSql2 = 'insert into users set ?' db.query(insertSql2, user, (err, res) => { if(err) return console.log(err.message) if(res.affectedRows === 1) { console.log("Success") } })
const user = {id: 12, username: 'de', password: '123de'} const updateSql = 'update users set username= ?, password= ? where id= ?' db.query(updateSql, [user.username, user.password, user.id], (err, res) => { if(err) return console.log(err.message) if(res.affectedRows === 1) { console.log("更新成功") } })
const user = {id: 12, username: 'dewen', password: '123dewen'} const updateSql = 'update users set ? where id= ?' db.query(updateSql, [user, user.id], (err, res) => { if(err) return console.log(err.message) if(res.affectedRows === 1) { console.log("更新成功") } })
const deleteSql = 'delete from users where id= ?' db.query(deleteSql, 12, (err, res)=> { if(err) return console.log(err.message) if(res.affectedRows === 1) { console.log('删除成功') } })
const deleteStr = 'update users set status=? where id=?' db.query(deleteStr, [1, 1], (err, res)=> { if(err) return console.log(err.message) if(res.affectedRows === 1) { console.log("执行标记删除成功") } })
|
Express 身份验证
Session 认证机制
配置express-session
中间件
1 2 3 4 5 6 7 8
| const session = require('express-session') app.use( session({ secret: '10wen.github.io', resave: false, saveUninitalized: true, }) )
|
向session
存储数据:中间件配置成功后,可通过 req.session
访问 session 对象,存储用户信息
1 2 3 4 5 6
| app.post('/api/login', (req, res) => { req.session.user = req.body req.session.isLogin = true
res.send({ status: 0, msg: 'login done' }) })
|
从session
取数据
1 2 3 4 5 6
| app.get('/api/username', (req, res) => { if (!req.session.isLogin) { return res.send({ status: 1, msg: 'fail' }) } res.send({ status: 0, msg: 'success', username: req.session.user.username }) })
|
清空 session
1 2 3 4 5
| app.post('/api/logout', (req, res) => { req.session.destroy() res.send({ status: 0, msg: 'logout done' }) })
|
JWT 认证机制
1. 安装:jsonwebtoken
、express-jwt
- jsonwebtoken 用于生成 JWT 字符串
- express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
1
| npm install jsonwebtoken express-jwt
|
2. 定义密钥:secret
1 2 3 4 5
| const jwt = require('jsonwebtoken') const expressJWT = require("express-jwt")
const secretKey = '10wen.github.io'
|
3. 生成 Token
1 2 3 4 5 6 7 8 9 10 11 12
| app.post('/api/login', function (req, res) { const userInfo = req.body const tokenStr = jwt.sign({username: userInfo.username}, secretKey, {expiresIn: '30s'}) res.send({ status: 200, message: '登录成功!', token: tokenStr }) })
|
4. JWT 字符串还原为 JSON 对象 (app.js
)
- 客户端访问有权限的接口时,需通过请求头的
Authorization
字段,将 Token 字符串发送到服务器进行身份认证
- 服务器可以通过 express-jwt 中间件将客户端发送过来的 Token 解析还原成 JSON 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
const expressJWT = require('express-jwt') const config = require('./config') app.use( expressJWT({ secret: config.jwtSecretKey, algorithms: ["HS256"], credentialsRequired: false, }).unless({ path: [ "/login", "/register", "/index", { url: /^\/articles\/.*/, methods: ["GET"] }, { url: /^\/content\/.*/, methods: ["GET"] }, ], }) );
|
5. 获取用户信息
- 当 express-jwt 中间件配置成功后,中间件就可以把解析出来的用户信息,挂载到
req.user
属性上,使用 req.user
对象,来访问从 JWT 字符串中解析出来的用户信息
1 2 3 4 5 6 7 8 9
| app.get('/admin/getinfo', function (req, res) { console.log(req.user) res.send({ status: 200, message: '获取用户信息成功!', data: req.user }) })
|
6. 捕获解析 JWT 失败后产生的错误
- 当使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行
- 通过 Express 的错误中间件,捕获这个错误并进行相关的处理
1 2 3 4 5 6
| app.use((err, req, res, next) => { if (err.name === 'UnauthorizedError') { return res.send({ status: 401, message: 'Invalid token' }) } res.send({ status: 500, message: 'Unknown error' }) })
|