第十节: Node框架: Express

2020-12-26  本文已影响0人  时光如剑

1. Express 介绍(了解)

Express 是一个基于 NodeJS平台的极简.灵活的web应用开发框架,可以实现非常强大的web服务器功能

1.1 原生Node服务器缺点
  1. 路由不方便制作,尤其是正则表达式的路由
  2. 静态资源服务器不方便
  3. 页面呈递不方便
1.2 Express框架的特点
  1. 提供了中间件来控制HTTP请求
  2. 定义路由表用于执行不同的HTTP请求动作(url=资源)映射
  3. 可以通过向模板传递参数来动态渲染HTML页面
  4. 拥有大量的第三方中间件对功能进行扩展

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.png
3.1.1 中间件的功能:
  1. 执行任何代码
  2. 修改请求和响应对象
  3. 终结请求-响应循环
  4. 调用堆栈中的下一个中间件
3.1.2 中间件的结构

使用中间件语法

​ app.use([path , ] callback [, callback...])

参数

  1. path 可选, 为路由的url, 如果省略将匹配到所有路径,

  2. 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 第三方中间件

有关第三方中间件,这里我们分析几个比较重要和常用的,知道这几个的使用,其它的也就会了。

  1. body-parser :解析body中的数据,并将其保存为Request对象的body属性。
  2. cookie-parser :解析客户端cookie中的数据,并将其保存为Request对象的cookie属性
  3. 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>"`)                 //  普通文本

注意: 如果返回数字: 会被当做状态码

  1. res.send(1); 这样写会报错()
  2. send() 方法只能出现一次,重复无效还报错,因为send内含end()结束响应

设置状态码,并返回内容

res.status(200).send("text")

send方法的好处

  1. send方法内部会自动检测响应内容的类型
  2. send方法会自动设置http状态码
  3. 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 视图模板
  1. 将渲染的视图发送给客户端
res.render("index")
  1. 将视图和数据合并后发送给客户端
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)

参数了解

  1. app: express 实例
  2. METHOD: http 请求方式
  3. PATH: 请求路径
  4. HANDLER: 路由匹配后执行的函数

示例:

app.get("/about.html",function(req,res){
    res.send("<h1>个人简介</h2>")
})
6.3 Router (重点) 路由模块化

express.Router 类可以创建模块化(独立的),可以挂载的路由对象.Router对象是一个完整的中间件和路由系统,因此常称其为一个"mini-app"

需求: 创建一个student路由模块,接受所有 student目录下的所有请求,响应数据

  1. 创建student模块

  2. 编写路由模块的代码

     // 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;
    
  3. 将编写好的路由模块引入到主模块,由主模块分配对应的请求到该模块去处理

    // 1. 引入vip路由模块
    var studentRouter = require('./routes/student');
    // 2. 分配student目录下的路由给studentRouter模块
    app.use('/student', studentRouter);
    
上一篇下一篇

猜你喜欢

热点阅读