最近在努力搞学习,所以可能记录会比较多哈哈哈,在看双越老师的 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.body:post请求的数据就是在这里面req.query:get请求的数据在这里面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-session和connect-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.slice
class 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端口')
})
先写那么多了,实在惭愧,上边实现中间件的代码都是老师课程上教的,会继续努力学习的!