第十节: Node框架: Express
1. Express 介绍(了解)
Express 是一个基于 NodeJS平台的极简.灵活的web应用开发框架,可以实现非常强大的web服务器功能
1.1 原生Node服务器缺点
- 路由不方便制作,尤其是正则表达式的路由
- 静态资源服务器不方便
- 页面呈递不方便
1.2 Express框架的特点
- 提供了中间件来控制HTTP请求
- 定义路由表用于执行不同的HTTP请求动作(url=资源)映射
- 可以通过向模板传递参数来动态渲染HTML页面
- 拥有大量的第三方中间件对功能进行扩展
2.Express 安装及基本使用
2.1 安装express
npm install express --save
2.2 express的使用
// 引入express框架
const express = require('express');
// 创建网站服务器
const app = express();
// 这里路由清单
app.get('/', function (req, res) {
// 返回数据用send方法
res.send("我是首页")
})
app.get('/music', function (req, res) {
res.send("音乐频道")
})
app.get('/news', function (req, res) {
res.send("新闻频道")
})
// 监听端口
app.listen(3000);
console.log("Server start at 3000 port")
3. 中间件(Middleware)
Express 是一个自身功能 极简,完全是由路由和中间件构成的一个web开发框架,从本质上来说,一个Express 应用就是在调用各种 中间件
3.1. 什么是中间件
中间件就是匹配路由之前或者匹配路由完成时所作的一系列的操作,我们可以把他叫做中间件,
中间件函数,可以访问请求对象,响应对象,也可以调用下一个中间件,一般被命名为next的变量.(next 尾函数, 执行下一个任务)
express框架就是一个有中间件构建起来的框架,整个框架全是中间件
中间件2.png3.1.1 中间件的功能:
- 执行任何代码
- 修改请求和响应对象
- 终结请求-响应循环
- 调用堆栈中的下一个中间件
3.1.2 中间件的结构
使用中间件语法
app.use([path , ] callback [, callback...])
参数
path 可选, 为路由的url, 如果省略将匹配到所有路径,
callback 中间件函数, 当路由匹配成功执行函数, 函数接受三个参数
calllback(request, response, next)
- request: HTTP请求对象
- response:HTTP响应对象
- next:处理完后交给下一个路由。若不调用则处理到此为止,不进行后续操作
示例
app.use('/', function(req,res,next){
})
3.2. 尾函数 next
如果在中间里不调用next函数,整个请求响应流程就会中断,不会再往后面执行
app.use(function (req, res, next) {
console.log(111)
console.log(222);
next(); // 如果不调用next,将不会执行下一个中间件,不会打印333,444
})
app.use(function (req, res, next) {
console.log(333)
console.log(444)
})
3.3. 下一个中间件执行完毕
中间件类似于过滤器,用于在客户端和应用程序之间处理请求和响应的方法。中间件的执行类似剥洋葱,但并非一层层的执行,而是以next
为分界,先执行本层next
之前的部分,当下一层中间件执行完毕后再执行本层next
之后的部分。
中间件洋葱图
中间件.png调用尾函数就会执行下一个中间件,下一个执行完毕后回来继续执行自己的函数
app.use(function (req, res, next) {
console.log(111)
next()
console.log(222);
})
app.use(function (req, res, next) {
console.log(333)
console.log(444)
})
/*
打印
111
333
444
222
*/
3.4. 中间件分类
3.4.1 内置中间件
express.static()
express.static 是用来处理静态资源文件。
// 推荐使用绝对路径
app.use(express.static(path.join(__dirname,'public')))
我们也可以给静态资源目录制定一个虚拟路径
// 第一个参数就是给静态资源目录制定的虚拟路径
app.use('/static',express.static(path.join(__dirname,'public')))
express.json()
处理json方式的传参
内置的json
中间件负责把带有JSON的请求中(即Content-Type=‘application/json’
)的数据提取出来,它基于body-parser
app.use(express.json());
express.urlencoded()
处理表单方式的传参
urlencoded
负责将通过urlencoded发送请求(即Content-Type=‘application/x-www-form-urlencoded’
)的数据提取出来,它基于body-parser
经过此中间件后,req.body
为解析后的json串,
实例:
const express = require("express")
const bodyparser = require("body-parser")
const app = express()
// 处理post请求表单传参方式的数据
app.use(express.urlencoded())
// 处理post 请求json 传参方式的数据
app.use( express.json())
//
app.use(function(req,res,next){
console.log(req.body)
res.send("首页")
})
app.listen(3000, function(){
console.log("Server start at 3000 port")
})
3.4.2 自定义中间件
在上面中间件结构中,我们知道了,中间件使用时的第二个参数是一个Function,然而,要自定义一个中间件,就是倒腾一番这个Function。
这个function总共有三个参数(req,res,next);
当每个请求到达服务器时,nodejs会为请求创建一个请求对象(request),该请求对象包含客户端提交上来的数据。同时也会创建一个响应对象(response),响应对象主要负责将服务器的数据响应到客户端。而最后一个参数next是一个方法,因为一个应用中可以使用多个中间件,而要想运行下一个中间件,那么上一个中间件必须运行next()。
app.use(function (req, res, next) {
console.log(111)
})
3.4.3 第三方中间件
有关第三方中间件,这里我们分析几个比较重要和常用的,知道这几个的使用,其它的也就会了。
- body-parser :解析body中的数据,并将其保存为Request对象的body属性。
- cookie-parser :解析客户端cookie中的数据,并将其保存为Request对象的cookie属性
- express-session :解析服务端生成的sessionid对应的session数据,并将其保存为Request对象的session属性
例子:
const express = require("express")
const bodyparser = require("body-parser")
const app = express()
// 处理post请求表单传参方式的数据
app.use(bodyparser.urlencoded({extends:true}))
// 处理post 请求json 传参方式的数据
app.use(bodyparser.json())
//
app.use(function(req,res,next){
console.log(req.body)
res.send("首页")
})
app.listen(3000, function(){
console.log("Server start at 3000 port")
})
3.4.4 错误处理中间件
在程序执行过程中,不可避免的会出现一些无法预料的错误,比如文件读取失败,数据库连接失败,错误处理中间件是一个集中处理错误的地方
// 自己手动的抛出一个错误
app.get("/", function(req,res,next){
throw new Error('服务器发生错误')
})
// 当程序出错的时候就会走错误中间件
app.use(function(err, req,res,next){
// console.dir(err)
res.status(500).send(err.message)
})
上面的错误例子是同步的错误, 如果是异步语句发生错误要调用next()并且传入错误参数
异步处理发生错误使用next手动触发错误中间件
app.get("/", function(req,res,next){
fs.readFile("./index.html",'utf8',(err,result) => {
// console.log(err,result)
if(err){
next(err)
}else{
res.send(result)
}
} )
})
app.use(function(err, req,res,next){
// console.dir(err)
res.status(500).send(err.message)
})
3.5.中间件的应用
3.5.1 路由保护
客户端在访问登录页面的时候,可以先使用中间件判断用户登录状态, 用户如果未登录,则拦截请求, 直接响应, 禁止用户进入登录页面
app.use(function(req,res,next){
// 判断用没有登录, 登录就走后面,没登录,就提示用户登录
if(cookiename){
next()
return;
}
res.send("你还没有登录,请登录")
})
3.5.2 网站维护公告
在所有路由之前最上面定义接受所有组件的中间件, 直接为客户端响应,网站正在维护中
app.use(function(req,res,next){
let hours = new Date().getHours()
console.log(hours)
if( 22 <= hours && hours <= 23 ){
res.send("网站正在维护,请于早上8点访问")
}
next()
})
3.5.3 自定义404 页面
在所有路由后面定义中间件, 处理404
app.use(function(req,res,next){
res.status(404).send("404, 你访问的页面不存在")
})
4. Express 请求与相应
4.1. Express 响应对象
响应对象是指服务器向客户端响应数据的对象,包含所有要响应的内容
4.1.1 send() 方法 (重点)
向页面发送各种数据
语法:
res.send(data) 可以返回任意类型数据
参数
响应内容
示例:
res.send(Buffer.from("hello world")); // 流数据
res.send({"name":"json"}); // JSON 数据
res.send(`"<p>普通文本</p>"`) // 普通文本
注意: 如果返回数字: 会被当做状态码
- res.send(1); 这样写会报错()
- send() 方法只能出现一次,重复无效还报错,因为send内含end()结束响应
设置状态码,并返回内容
res.status(200).send("text")
send方法的好处
- send方法内部会自动检测响应内容的类型
- send方法会自动设置http状态码
- send方法会帮我们自动设置响应内容的内容类型及编码
4.1.2 sendFile() 方法
响应文件
除了可以直接返回内容外,我们还可以直接返回文件
语法:
res.sendFile( path, callback)
path: 返回文件的路径,必须是绝对路径,否则会报错
callback : 回调函数, 接受一个错误的参数
app.get("/data",(req,res) => {
res.sendFile(path.join(__dirname,"./students/100002.json"),(err)=>{
console.log(err)
})
})
4.1.3 json() 方法
我们除了可以利用send返回JSON数据外,还可以单独使用json方法响应JSON数据
返回JSON数据,会自动设置响应头
语法:
res.json(jsonData) // 返回json对象,一般针对ajax应用
实例:
// 可以是真正的JSON数据
res.json(JSON.stringify({name:"aa"}))
// 如果是对象会自动被转为JSON数据
res.json({name:"小明",age: 8})
当然了,也可以通过sendFile 方法响应json数据
res.sendFile(`${__dirname}/data.json`)
4.1.4 redirect () 重定向路径
重定向到指定的URL路径(浏览器地址变为设置的地址)
语法
res.redirect("/user")
// 第一个参数也可以是状态码
res.redirect(301, "/user")
示例:
app.get("/data",(req,res,next) => {
// 路由重定向到/json路由上
res.redirect(301,"/json")
})
app.get("/json",(req,res) => {
res.json({name:'aa'})
})
4.1.5 download 下载
语法
res.download("./xxx.zip") 下载当前目录下的xxx.zip文件
app.get("/data",(req,res) => {
res.download(`./students.zip`)
})
4.1.6 end () 结束响应方法
就是结束前后端的连接
res.end()
结束响应的时候也可以同时响应数据
res.end("结束")
4.1.7 res.jsonp ()
传送JSONP响应,
语法:
res.jsonp(jsonData)
jsonp返回给前端的依然是json数据, 但是前端在使用jsonp的使用需要路径传递上callback参数
如果不传递得到的就是json数据
如果传递了callback那么得到的就是以callback值为函数名执行
示例:
app.get("/jsonp",(req,res,next) => {
console.log(11)
res.jsonp({name:'wuwei'})
})
不加callback参数的请求结果
{
"name": "wuwei"
}
添加callback参数的请求结果
typeof wuwei === 'function' && wuwei({"name":"wuwei"});
4.1.8 render 视图模板
- 将渲染的视图发送给客户端
res.render("index")
- 将视图和数据合并后发送给客户端
res.render("index",{
username: "HAHA"
})
例子:
// 路由
var data = [
{name: "小明",age: 10},
{ name: "小红",age: 12},
{name: "小蓝",age: 11},
];
res.render("list", {
users: data
})
});
<!-- ejs模板 -->
<% for(var item of users){ %>
<li>姓名:<%= item.name%>-年龄:<%= item.age %></li>
<% } %>
4.2. Express 请求对象
req(request) 对象包含了一次请求中的所有数据(http 请求头信息,请求参数)
4.2.1 获取url地址中的参数(重点)
Express 框架中使用req.query 就可以获取URL方式传递的请求传值,并且会将GET参数转为对象返回
语法:
语法:
req.query
使用:
url: http://localhost:3000/user?name=07
获取参数: req.query
示例
// 例如: http://localhost:3000/user?name=07
app.get("/user", (req,res) => {
console.log(req.query); // {name: 07}
res.send(req.query)
})
4.2.2 获取POST的参数(重点)
Express中接收POST请求参数需要借助第三方的包,body-parser
语法:
req.body
比如
// 首先需要引入第三方的包,
const bodyparser = require("body-parser");
// 拦截所有请求
// 配置body-parser模块
// extended: false ,方法内部使用querystring内置模块处理请求参数格式
// extended: true 方法内部使用第三方qs模块处理请求参数
app.use(bodyparser.urlencoded({extended: false}))
app.use(bodyparser.json())
// 还有exprss 框架提供的处理post的内置中间件
app.use(express.urlencoded())
app.use(express.json())
// 接受请求
app.post('/add', (req,res) => {
// 请求参数
console.log(req.body)
})
4.2.3 路由参数
动态路由(伪静态页面)
语法:
req.params.参数名
定义路由
// :id 就是一个占位符,路由参数的属性 app.get("/first/:id", function (req, res) { var id = req.params.id; res.send("你动态路由参数是:" + id) });
4.2.4 RESTful 路由风格
所谓的路径参数就是最近比较火的RESTful路由风格
以前的操作是一个路由对应一个功能,
如果我要对同一个学生操作增删改查,就得定义如下的路由
http://127.0.0.1/showstudent?id=10001 // 显示学生信息路由http://127.0.0.1/delstudent?id=10001 // 删除学生信息路由http://127.0.0.1/addstudent?id=10001 // 新增学生信息路由http://127.0.0.1/updatestudent?id=10001 // 修改学生信息路由
现在比较流行的是RESTful路由风格处理
简单理解就是:对于同一个学生的操作,都在同一个URL下进行,通过判断HTTP类型的不同,来决定做不同的事情
http://127.0.0.1/students/10001 // GET请求 访问学生信息
http://127.0.0.1/students/10001 // POST请求 修改学员信息
http://127.0.0.1/students/10001 // put请求, 查看学员是否被占用
http://127.0.0.1/students/10001 // delete请求 删除学员信息
那么路径上的100001就是我们参数的部分, 这样比原本传统的参数要美观,
我们管这种参数叫路由参数
4.3. 路由路径的匹配
4.3.1 字符串匹配模式
// 1. 固定匹配
/*
/index 可以匹配成功
/index/ 可以匹配成功
/index/a 不可以匹配
*/
app.get("/index", (req,res) => {})
// 2. 匹配所有路径
app.get("*", (req,res) => {})
// 3. 匹配以/index开头的路径
/*
/index 可以匹配成功
/index123 可以匹配成功
/index/a/b 可以匹配成功
*/
app.get("/index*", (req,res) => {})
// 4. 匹配以 /index/ 开头的路径
/*
/index 不能匹配成功
/index/ 可以匹配成功
/index/a/b 可以匹配成功
*/
app.get("/index/*", (req,res) => {})
// 5. ? 前面的字符或组出现0~1 次
/*
/abc 可以匹配成功
/ac 可以匹配成功
*/
app.get("/ab?c", (req,res) => {})
// 6. + 前面的字符或组出现1~多 次
/*
/abc 可以匹配成功
/abbbc 可以匹配成功
/ac 不能匹配成功
*/
app.get("/ab+c", (req,res) => {})
// 7. * 表示任意个数的任意字符
/*
/abc 可以匹配成功
/ab123c 可以匹配成功
*/
app.get("/ab*c", (req,res) => {})
// 8. ()里的内容为一组
/*
/abcd 可以匹配成功
/ad 可以匹配成功
/abd 不能匹配成功
/acd 不能匹配成功
*/
app.get("/a(bc)?d", (req,res) => {})
4.3.2 正则匹配模式
//1. 包含index的路径
/*
/index 可以匹配
/aindexb 可以匹配成功
/a/index/b 可以匹配成功
*/
app.get(/index/, (req,res) => {})
// 2. 以.html后缀名结尾的路径
/*
/index.html 可以匹配
/index/a.html 可以匹配成功
*/
app.get(/.*\.html$/, (req,res) => {})
4.4. 路由器的处理程序
4.4.1 单处理程序
// 方式一
app.get("/", (req,res) => {})
// 方式二
let handler = (req,res) => {}
app.get("/", handler)
4.4.2 多处理程序
除非是最后一次处理,否则请不要执行res.send()等发送指令
除非是最后一次处理,否则请不要忘记next形参 与next() 指令
app.get("/", (req,res,next) => {
console.log("handle 1")
next()
},(req,res,next) => {
console.log("handle 2")
next()
},(req,res) => {
res.send("Hello wrold")
})
或者可以这么写
let handler1 = (req,res,next) => {
console.log("handle 1")
next()
}
let handler2 = (req,res,next) => {
console.log("handle 2")
next()
}
let handler3 = (req,res,next) => {
res.send("Hello wrold")
}
app.get("/", [handler1,handler2,handler3])
5. express 子应用程序
express模块 每一次执行都会创建一个应用
将子应用通过主应用的use方法挂在到主应用上
6. express 路由
6.1 路由的理解
路由是指接收用户请求,处理用户数据,返回结果给用户的一套程序,
后端路由的核心是URL
app.get("/",(req,res,next) => {
})
6.2 express路由的使用
虽然现在可以通过app.get或app.post 定义路由了, 但是在一个项目中, 路由的数量是非常多的, 如果把所有的路由都放在同一文件中,那将是非常可怕的事情,
所以express 为了解决这个问题, 提供了模块化构建路由的方式, 我们可以根据条件将路由进行分类
express 路由使用语法
app.METHOD(PARH, HANDLER)
参数了解
- app: express 实例
- METHOD: http 请求方式
- PATH: 请求路径
- HANDLER: 路由匹配后执行的函数
示例:
app.get("/about.html",function(req,res){
res.send("<h1>个人简介</h2>")
})
6.3 Router (重点) 路由模块化
express.Router 类可以创建模块化(独立的),可以挂载的路由对象.Router对象是一个完整的中间件和路由系统,因此常称其为一个"mini-app"
需求: 创建一个student路由模块,接受所有 student目录下的所有请求,响应数据
-
创建student模块
-
编写路由模块的代码
// 1. 引入模块 const express = require("express") // 2. 实例化路由对象 const router = express.Router(); // 3. 编写路由线路挂载到路由对象上 router.get("/first.html", function (req, res) { res.send("<h2>一年级</2>") }); router.get("/second.html", function (req, res) { res.send("<h2>二年级</2>") }) // 4. 暴露对象 module.exports = router;
-
将编写好的路由模块引入到主模块,由主模块分配对应的请求到该模块去处理
// 1. 引入vip路由模块 var studentRouter = require('./routes/student'); // 2. 分配student目录下的路由给studentRouter模块 app.use('/student', studentRouter);