关于 Express,关于中间件

298 阅读6分钟

最近在努力搞学习,所以可能记录会比较多哈哈哈,在看双越老师的 Node 的课程嘛,所以想写一下关于 Express 的笔记,咋个说呢,对 Express 还是蛮有感情的,因为在学习 Koa 之前,简单搭建服务器,做一些小测试都是用的它。真是,听君一席话,如听一席话,哈哈哈哈。

虽然因为难以处理异步的原因,现在都在说 Koa ,但是既然我花了时间学,就得有收获是吧

1、简单 Demo

const express = require('express')
​
const app = express()
app.use((req,res,next) => {
    res.json({
        username:'你是我的小可爱'
    })
})
app.listen(8080)

上边的代码就是一个最简单用 Express 搭建的服务器了,再来看一下原生的 Node.js 搭建服务器

var http = require("http");
​
http.createServer(function (req, res) {
  // 设置 HTTP 头部,状态码是 200,文件类型是 html,字符集是 utf8
  res.writeHead(200, {
    "Content-Type": "text/html;charset=UTF-8"
  });
  res.end('你是我的小可爱');
}).listen(8080);

乍一看,也没有简单很多哈,代码量都差不多嘛,但作为一个框架,肯定做了很多封装,接着写下去哈哈哈

2、更为强大的 res 和 req

在原生的 Node.js 中想要获取请求体中的数据,还得自己逐个去解析,但是用 Express 中间件处理过后,你可以直接去取,比如:

app.use(cookieParser());//解析 cookie
app.use(express.json());//解析req中的请求数据
app.use(express.urlencoded({ extended: false }));
  • req.bodypost 请求的数据就是在这里面
  • req.queryget 请求的数据在这里面
  • req.cookie:客户端的 cookie,就在这里边,你再也不用去 req.headers.cookie 里面去解析那一大串臭臭的字符串了

响应对象也升级了,细心的小伙伴会发现,上边的 Demo 中,原生 JS 还使用了 writeHead 来编写响应头,为了不乱吗,但是在 Express 里面不用去担心,不仅如此,原生 Node.js 一般只能响应 字符串 或者 buffer 流。但是:

  • res.json:返回 json 格式数据,再也不用 JSON.stringify
  • res.send:可以返回,字符串,buffer,对象
  • res.sendFile:可以返回一个文件

3、 更多强大的中间件

关于因为也没有太多其他额外的接触,所以列举的都是老师课上用到过的

  • 咱都知道,session 是一种解决方案,不是一种技术,在原生 Node.js 里面 req.session 只是我们给 req 添加的一个自定义属性,它的名字叫 session 而已。咱还得处理 cookie、session、redis 之间修改存储的麻烦关系,用上 express-sessionconnect-redis 之后,咱就只需要写配置了。当然,这些中间件咋个用的也得学哈哈哈。
  • 然后就是日志了,原生里面,咱得自己封装函数,日志是啥格式你自己要设计,使用 morgan 后,有各种日志格式任你选择。

4、中间件的原理

中间件很好理解的,就像流水线一样,有很多岗位,岗位上可以有不止一个工人。

4.1 使用理解

可以看一下下面的伪代码

app.use(cookieParser())
app.use((req,res,next) => {
    ...
    next()
})
​
app.use('/api',(req,res,next) => {
    ...
    next()
})
function loginCheck(req,res,next){
    setTimeout(() => {
        ...
        next()
    })
​
}
app.get('/api/get-cookie',loginCheck,(req,res,next) => {
    ...
    res.json({
        errno:0,
        data:req.cookie
    })
})

app.use,app.get 这些就是岗位,loginCheck,cookieParse() ,(req,res,next) =>...,这些就是工人(中间件),产品自带编码,也就是请求的 url,例如 /api/get-cookie,它可以命中上边所有的岗位,是否命中,就看路由的匹配关系了,"/" 或者不传可以被所有命中。命中之后,工人就干活,也就是执行函数,工人干完活之后,推给(执行 next())下一个被命中的岗位。像 cookieParser() 这种别人封装好的中间件,在内部肯定也是执行了 next() 的。

4.2 代码实现中间件

说一下老师的思路,我感觉还蛮清晰的,首先定义的是一个类,在构造函数里面存了几个数组,用来存储所有的中间件,如何存进去的呢,暴露的 use,get,post 执行的时候,都执行 register ,将中间件存进对于的数组。然后就是 listen 了,调用 listen 方法时会使用原生 Node.js 创建服务,在回调函数 callback 中,会对 res 等进行取值封装,取出用户请求的 url 后,根据请求 method 和 路由筛选出符合要求的中间件放进一个另一个数组,然后一次执行它们,实现 next() 函数很有意思,很想一个递归,只要每个中间件都有 next() 就能一直递归下去,知道数组为空

const http = require('http')
const slice = Array.prototype.sliceclass LikeExpress {
    constructor(){
        //存放中间件的列表
        this.routes = {
            all:[],//存放 app.use(....)里面的中间件
            get:[],
            post:[]
        }
    }
    register(path) {
        const info = {}
        if(typeof path === 'string'){
            info.path = path
            //从第二个参数开始,转化为数组,存入stack
            //argument是一个类数组,下面的是将他转化为一个真正的数组
            info.stack = slice.call(arguments,1)
        } else {//第一个参数不是路径字符串,就默认是'/'
            info.path = '/'
            //从第一个参数开始,转化为数组,存入stack
            info.stack = slice.call(arguments,0)
        }
        return info
    }
​
    //三个函数都是注册中间件的函数,所以我们统一放在 register函数中处理
    use() {
        const info = this.register.apply(this,arguments)//这里我有点疑惑,目的只是为了传arguments的话,直接传不就好了,非要用个apply
        this.routes.all.push(info)//然后存到上边定义的数组中
    }
    get() {
        const info = this.register.apply(this,arguments)
        this.routes.get.push(info)
    }
    post() {
        const info = this.register.apply(this,arguments)
        this.routes.post.push(info)
    }
    match(method,url){
        let stack = []
        if(url === '/favicon.ico'){//浏览器默认请求那个小图标的
            return stack
        }
        //获取 routes
        let curRoutes = []
        curRoutes = curRoutes.concat(this.routes.all)
        curRoutes = curRoutes.concat(this.routes[method])
​
        curRoutes.forEach(routeInfo => {
            if(url.indexOf(routeInfo.path) === 0){
​
                //匹配 url === '/api/abc' 且 routeInfo.path === '/' 或 '/api' 或 '/api/abc'
                stack = stack.concat(routeInfo.stack)
            }
        })
        return stack
    }
    //核心next机制
    handle(req,res,stack){
        const next = () => {
            //拿到第一个中间件
            const middleware = stack.shift()
            if(middleware) {
                //执行中间件函数,因为存进去的就类似于loginCheck,是一个人函数声明,执行它 当然就要加上括号和参数了
                middleware(req,res,next)//这里的思路就是手动递归?哈哈哈,你一直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')
const app = express()
app.use((req,res,next) => {
    console.log('请求开始',req.method,req.url)
    next()
})
app.use((req,res,next) => {
    req.cookie = {
        useId:'111222'
    }
    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-cookie')
    res.json({
        errno:0,
        data:req.cookie
    })
})
app.listen(8000,() => {
    console.log('服务运行在8000端口')
})

先写那么多了,实在惭愧,上边实现中间件的代码都是老师课程上教的,会继续努力学习的!