第十节: Node框架: Express

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(`"

普通文本

"`) // 普通文本

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

  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
   })
 });


<% for(var item of users){ %>
    
  • 姓名:<%= item.name%>-年龄:<%= item.age %>
  • <% } %>


    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("

    个人简介

    ") })


    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("

      一年级") }); router.get("/second.html", function (req, res) { res.send("

      二年级") }) // 4. 暴露对象 module.exports = router;

    3. 将编写好的路由模块引入到主模块,由主模块分配对应的请求到该模块去处理

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

    你可能感兴趣的:(第十节: Node框架: Express)