• 有问题请联系QQ:2374146085
  • 有问题请联系QQ:2374146085

Node-Express

1年前 (2022-01-29) 1418次浏览 已收录 0个评论 扫描二维码

导读

Express创建服务器、监听GET、POST请求、把内容响应给客户端、获取URL中携带的查询参数、动态参数、托管静态资源、模块化路由、中间件、全局生效、局部生效、中间件的分类、自定义中间件、文件上传、日志保存、cors中间件解决跨域问题、cors响应头部、JSONP接口、session认证、jwt认证


Express

expresss和node内置的http模块类似,是专门用来 创建web服务器的。

创建服务器

// 导入express
const express = require('express')

//创建web服务器
const app = express()

//调用app.listen(端口号,启动成功后的回调函数)
app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})

监听GET请求

app.get('请求URL',(req,res,next)=>{
    
})
  • req:请求对象(包含了与请求相关的属性和方法
  • res:响应对象(包含了与响应相关的属性和方法

监听POST请求

// req:请求对象(包含了与请求相关的属性和方法
// res:响应对象(包含了与响应相关的属性和方法
app.post('请求URL',(req,res)=>{

})

把内容响应给客户端

// 导入express
const express = require('express')

//创建web服务器
const app = express()

// 把内容响应给客户端.必须有res.send/res.json,否则客户端将一直是挂起状态,而koa不是挂起状态
app.get('/user',(req,res)=>{

    //res.status(500) 设置响应码
    res.send({name:'wxl'}) //此时客户端接收到的类型是字符串
    //res.json({name:'wxl'}) 此时客户端接收到类型就是json,常用
})
app.post('/user',(req,res)=>{
    res.send('请求成功')
})

//调用app.listen(端口号,启动成功后的回调函数)
app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})

获取URL中携带的查询参数

  • req.query默认是一个空对象
  • 客户端使用?name=z&age=20这种字符串形式发送给服务器的餐水
  • 可以通过req.query对象访问到
  • req.query.name.  req.query.age
//获取URL中携带的查询参数
app.get('/',(req,res)=>{
    console.log(req.query)
    res.send(req.query)
})

获取URL中动态参数

  • 通过req.params对象,可以访问到URL中,通过:匹配的动态参数
  • req.params默认是一个空对象。里面存放在通过:动态匹配的参数值
app.get('/user/:id/:name',(req,res)=>{
    console.log(req.params)
    res.send(req.params)
})

/**
{
    "id": "3232",
    "name":"wxl"
}
 */

托管静态资源

通过express.static(),就可以 创建一个静态资源服务器

app.use(express.static('public'))
//如果要托管多个静态资源目录,就写几次上面的代码
  • 这样就可以访问public目录中所有的文件了
  • 但是访问url中不会包含publc,例如/public/img/wxl.jpg
  • 127.0.0.1/img/wxl.jpg

挂载路径前缀

app.use('/public',express.static('public'))
  • 这样就可以通过带有 /public前缀地址来访public目录中的文件了:
  • 127.0.0.1/public/img/wxl.jpg

模块化路由

是客户端的请求与服务器处理函数之间的映射关系

  • 它有完整的中间件系统
  • 路由由3部分组成,分别 是请求的类型、请求的url地址,处理函数
  • 创建路由模块对应的js文件
  • 调用express.Router()函数创建路由对象
  • 向路由对象上挂载具体的路由
  • 使用module.exportss向外共享路由 对象
  • 使用app.use()函数注册路由 模块

创建路由模块

var express = require('express')
var router = express.Router()

router.get('/user/list',(req,res)=>{
    res.send('get user list')
})

router.post('/user/list',(req,res)=>{
    res.send('post user list')
})

module.exports = router

注册路由模块

// 导入express
const express = require('express')
//创建web服务器
const app = express()

//导入上步创建的路由模块
const router = require('./router')
//注册路由模块
app.use(router)

//调用app.listen(端口号,启动成功后的回调函数)
app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})

app.user()函数就是用来注册全局中间件

为路由模块添加前缀

和静态托管资源前缀规则一样。

app.use('/api',router)

中间件

它本质上就是一个function处理函数(回调函数),中间件函数的形参列表中,必须包含next参数,而前面所说的路由处理函数中只包含req和res。

  • 如果当前中间件功能没有结束请求(res.end),则必须调用next()将控制权传递给下一个中间件功能,否则,请求将会被挂起.
  • 中间件可以执行的任务
    • 执行任何代码
    • 更改请求request和响应response对象
    • 结束请求 -响应周期(返回数据)
    • 调用栈中下一个中间件
  • 如何将一个中间件应用到我们的应用程序中
    • app.use、router.use、app.methods、router.methods
    • methods指的是请求方式,例如app.get()

//单个中间件
app.get('/',(req,res,next)=>{
    next()
})

next函数的作用

它是实现多个中间件连续调用的关键,它表示 把流转关系 转交 给下一个已经匹配到的中间件或路由,即上次一个的输出作为下一次的输入 。

对于每一个请求,会匹配到它所对应的所有中间件,其中第一个中间件处理完毕后,再通过next(),会转交给匹配到的下一个中间件.

定义中间件函数

  • 在当前中间件的业务处理完毕后,必须调用next()函数
const n = function(req,res,next){
    next()
}

路径中间件

app.use('/home',(req,res,next)=>{
  res.end('home 中间件')
})

全局生效的中间件

客户端发球的任何请求 ,到达服务器之后,都会触发的中间件,叫做全局生效中间件。

通过调用app.use(中间件函数),即可定义一个全局生效的中间件。

app.use(n)

演示全局生效中间件

// 导入express
const express = require('express')
//创建web服务器
const app = express()

const n = function(req,res,next){
    console.log('触发了中间件')
    //将流转关系,转交给下一个中间件或者路由,由于下面没有中间件了,所以
    //直接把流转关系转交给路由了,即'/'
    next()
}
app.use(n)

app.get('/',(rea,res)=>{
    console.log('调用了 / 这个路由')
    res.send('home')
})
//调用app.listen(端口号,启动成功后的回调函数)
app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})

/**
 * 触发了中间件
 * 调用了 / 这个路由
 */

中间件的作用

多个已经匹配到的中间件之间,共享同一份req和res,这样,就可以在上游的中间件中,统一为res和res对象添加自定义属性或者方法,供下游的中间件或路由使用。

// 导入express
const express = require('express')
const req = require('express/lib/request')
//创建web服务器
const app = express()

const n = function(req,res,next){
    //获取请求到达服务器的时间
    const time = Date.now()
    //为req对象,挂载自定义属性,从而把时间共享给后面所有路由
    req.startTime = time
    next()
}
app.use(n)

app.get('/',(req,res)=>{
    res.send('home-'+req.startTime)
})
//调用app.listen(端口号,启动成功后的回调函数)
app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})

定义多个全局中间件

客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用。

app.use(n)
app.use(n2)
app.use(n3)

局部生效的中间件

不使用app.use()定一个中间件,就是局部生效中间件

// 导入express
const express = require('express')
//创建web服务器
const app = express()

const n = function(req,res,next){
    console.log('调用了局部生效中间件')
    next()
}

// n这个中间件只在当前路由生效,即局部生效中间件
app.get('/',n(req,res)=>{
    res.send('home')
})
// n这个中间件不会影响这个路由
app.get('/user',(req,res)=>{
    res.send('user')
})

//调用app.listen(端口号,启动成功后的回调函数)
app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})

定义多个局部中间件

app.get('/',n,n2,(req,res)=>{
    res.send()
})
app.get('/',[n,n2],(req,res)=>{
    res.send()
})

中间件的分类

应用级别的中间件

通过app.use()或者app.get()后者app.post(),绑定到app实例上的中间件,叫做应用级别中间件。

路由级别的中间件

绑定到express.Router()实例上的中间件。

// 导入express
const express = require('express')
//创建web服务器
const app = express()

//导入路由模块
const router = require('./router')
router.use((req,res,next)=>{
    next()
})

//注册路由模块
app.use(router)

//调用app.listen(端口号,启动成功后的回调函数)
app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})

错误级别的中间件

专门中来捕获整个项目中发生的异常错误,从而防止因异常而程序崩溃的问题。

// 导入express
const express = require('express')
//创建web服务器
const app = express()

app.get('/',(req,res,next)=>{
    if(true){res.send()}
    else{
       next(new Error('user_is_null')) //next只有在错误中间件里才能传入参数
    }
})

//错误级别的中间件必须注册在所有路由之后
app.use((err,req,res,next)=>{
    let status = 300;
    let message = ""
    switch(err.message){
        case 'user_is_null':
            message='user is null!'
            status = 202
            break
        default:
            message='error'
            status=400
    }
    console.log('发生了错误',message)
    res.status(status)//错误码
    res.json({
      code:status,
      message:message
    })
})

//调用app.listen(端口号,启动成功后的回调函数)
app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})
除了错误级别的中间件,其他的中间件必须在路由之前进行配置

Express内置的解析中间件

  • express.static快速托管静态资源的内置中间件
  • express.json 解析json个格式的请求体数据
  • express.urlencoded解析url-encoded格式的请求体数据
  • form-data的body解析,express没有内置解析中间件,需要自己下载npm install multer

固定配置格式

//a、配置解析application/json格式数据的内置中间件
app.use(express.json())
//b、配置解析application/x-www-form-urlencoded格式数据的内置中间件
app.use(express.urlencoded({extended:false}))

//c、配置解析form-data格式
const multer = require('multer')
cosnt upload = multer()
//[注意] 下边的upload.any(即multer)只能这么局部使用,而不能使用到全局中间上

app.post('/login',upload.any(),(req,res,next)=>{
    console.log(req.body) //就能拿到req.body分别解析(a、b、c)后的数据了
}

express.json
  • 默认情况下,如果不配置解析表单数据的中间件,则req.body默认等于undefined

// 导入express
const express = require('express')
//创建web服务器
const app = express()

//解析表单中 json 格式的数据 
app.use(express.json())

app.post('/user',(req,res)=>{
    //配置好解析json的中间件后,就可以访问json数据了
    console.log(req.body)
    res.send('user')
})

//调用app.listen(端口号,启动成功后的回调函数)
app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})
/**
 * server running at http://127.0.0.1
 * { name: 'wxl', age: 30 }
 */
express.urlencoded

// 导入express
const express = require('express')
//创建web服务器
const app = express()

app.use(express.urlencoded({extended:false}))

app.post('/user',(req,res)=>{
    console.log(req.body)
    res.send('user')
})

//调用app.listen(端口号,启动成功后的回调函数)
app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})
/**
 * [Object: null prototype] { name: 'wxl', age: '30' }
 */

自定义中间件

封装自己的中间件diy-body.js

const qs = require('querystring')
//node内置querystring模块 ,通过这个模块提供的parse()函数,
// 可以把查询到的字符串,解析成对象格式

/**
 * 在中间件中,需要监听req对象的data事件,来获取 客户端发送到服务器的书就
 * 如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后 ,分批发送给服务器,
 * 所以data事件可能会触发多次,每一次触发data事件,获取到的数据只是完整数据的一部分,
 * 需要收到对接收到数据进行拼接
 */

const diyBodyParser =(req, res, next) => {
  let str = ''

  //监听req对象的data事件(客户端发送过来的新的请求体数据)
  req.on('data', (d) => {
    str += d
  })

  //监听req的end事件
  //当请求体数据接收完毕,会自动触发req的end事件,因此我们可以在req的end事件中,
  //拿到并处理完整的请求体数据
  req.on('end', () => {
    //在str中存放的是 完整的请求体数据
    const body = qs.parse(str)
    console.log(body)
    //将解析出来的请求体对象,挂载为req.body属性,供下游使用
    req.body=body
    next()
  })
}

module.exports = diyBodyParser

server.js使用自定义中间件

// 导入express
const express = require('express')
const diyBody = require('./diy-body.js')
//创建web服务器
const app = express()

app.use(diyBody)

app.post('/user',(req,res)=>{
    res.send(req.body)
})
//调用app.listen(端口号,启动成功后的回调函数)
app.listen(80, () => {
  console.log('server running at http://127.0.0.1')
})

/**
 * server running at http://127.0.0.1
 * [Object: null prototype] { name: 'wxl', age: '30' }
 */

文件上传

一般form-data的body形式主要用于文件上传,所以还是借助multer

const express = require('express')
const multer = require('multer')

const app = express()

const storage = multer.diskStorage({
    destination:(req,file,cb)=>{
        cb(null,'./imgs') //存储的位置
    },
    filename:(req,file,cb)=>{
        cb(null,Math.random()+'diy_name.png') //存储的文件名
    }
})
const upload = multer({
    // dest:'./imgs' 存储的位置(简单配置)
    storage:storage //复杂配置
})

//通过中间件处理上传的单个文件
app.post('/upload',upload.single('file_key_name'),(req,res,next)=>{
    console.log(req.file)
    res.end('上传成功')
})

//处理上传的多个文件
app.post('/uploads',upload.array('file_key_name'),(req,res,next)=>{
    console.log(req.files)
    res.end('上传成功')
})

app.listen(9000,()=>{
    console.log('启动成功')
})

日志

const express = require('express')
const morgan = require('morgan')
const fs = require('fs')

const app = express()

const writerStream = fs.createWriteStream('./logs/1.log',{
    flags:'a+'
})

app.use(morgan('combined',{stream:writerStream}))

app.post('/home',(req,res,next)=>{
    res.end('hello')
})

app.listen(9000,()=>{
    console.log('启动成功')
})

CORS跨域资源共享

使用cors中间件解决跨域问题

  • 运行npm install cors 安装中间件
  • 使用 const cors = require(‘cors’) 导入中间件
  • 在路由之前 调用app.use(cors())配置中间件
// 导入express
const express = require('express')
//创建web服务器
const app = express()

//一定要在路由之前,配置cors中间件
const cors = require('cors')
app.use(cors)

//导入路由模块
const router = require('./router')
router.use((req,res,next)=>{
    next()
})

//注册路由模块
app.use(router)
app.get('/list',(req,res)=>{
    res.send('hello')
})


//调用app.listen(端口号,启动成功后的回调函数)
app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})

CORS响应头部-Access-Control-Allow-Origin

允许访问该服务端资源的外域URL

可以设置的参数为 url | *
res.setHeader('Access-Control-Allow-Origin','http://wxl.com')

Access-Control-Allow-Headers

默认情况下,cors仅支持客户端向服务端发送9个请求头,如果想发送其他请求头,需要 进行声明,例如:

res.setHeader('Access-Control-Allow-Headers','Content-Type, X-Custom-Header')

Access-Control-Allow-Methods

默认情况下,cors仅支持客户端发起GET、POST、HEAD请求,如果还希望发起PUT、DELETE等请求,必须进行以下配置

res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD')
 // res.setHeader('Access-Control-Allow-Methods','*')

简单请求

同时满足以下两大条件的请求,就属于简单请求

  • 请求方式:GET、POST、HEAS三者之一
  • http头部信息,不能是以下几种字段:不是自定义的头部字段、….

预检请求

只要符合以下任何一个条件的请求,都需要进行预检请求。

  • 请求方式为GET、POST、HEAD之外的请求Method类型
  • 请求头中包含自定义的头部字段
  • 向服务器发送了application/json格式的数据
在浏览器与服务器真是通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该次请求,当服务器成功响应预检请求后,才会发送真正的请求,并且携带真实的数据

JSONP接口

浏览器通过<script>标签的src属性,请求服务器上的数据,同时,服务器返回一个函数的调用。

  • JSONP不属于真正的Ajax请求,因为它没有使用XMLHttpRequest对象
  • JSONP仅支持GET请求。
如果项目中已经配置了cors,为了防止冲突,必须在配置cors中间之前声明JSONP的接口,否则JSONP接口会被处理 成开启了CORS的接口

实现JSONP接口的步骤

  • 获取客户端发送过来的回调函数的名字
  • 得到要通过JSONP形式发送给客户端的数据
  • 根据前两步得到的数据,拼接出一个函数调用的字符串
  • 把上一步拼接得到的字符串,响应给客户端的<script>标签进行解析执行
const express = require('express')
const app = express()

//必须在Cors中间件之前配置 JSONP请求
app.get('/user/list',(req,res)=>{
    //得到函数的名称
    const funName = req.query.callback
    //定义要发送给客户端的数据对象
    const data = {name:'wxl'}
    //拼接出一个函数的调用
    const scriptStr = `${funName}(${JSON.stringify(data)})`
    //把拼接的字符串你,响应给客户端
    res.send(scriptStr)
})

//一定要在路由之前,配置cors中间件
const cors = require('cors')
app.use(cors)

//导入路由模块
const router = require('./router')
router.use((req,res,next)=>{
    next()
})

//注册路由模块
app.use('/user',router)
app.get('/list',(req,res)=>{
    res.send('hello')
})


app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})


前后端的身份认证

session认证

const express = require('express')
const app = express()
app.use(express.json())
const session = require('express-session')
app.use(session({
    secret:'keyboard',//secret属性的值可以为任意字符串
    resave:false,//固定写法,下同
    saveUninitialized:true
}))

app.post('/login',(req,res)=>{
    console.log(req.body)
    //判断用户提交的登录信息是否正确
    if(req.body.username !== 'admin' || req.body.password !== '123456'){
        return res.send({
            status:1,
            msg:'登录失败'
        })
    }
    req.session.user = req.body //将用户的信息,存储到session中,即服务器中。
    req.session.islogion=true //将用户的登录状态,存到session中

    res.send({status:0,msg:'登录成功'})
})

//获取用户名
app.get('/username',(req,res)=>{
    //判断用户是否登录 
    if(!req.session.islogion){
        return res.send({
            status:1,
            msg:'未登录'
        })
    }
    res.send({
        status:0,
        msg:'success',
        username:req.session.user.username
    })
})

app.post('/logout',(req,res)=>{
    //清空 当前客户端 对应的session信息
    req.session.destroy()
    res.send({
        status:0,
        msg:'退出登录成功'
    })
})

app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})

session认证的缺点

  • copkie会被附加在每一个http请求中,增加了流量损耗
  • cookie是明文传输,存在安全性的问题
  • cookie大小限制是4kb
  • 对于 浏览器外的其他客户端(比如ios,安卓),必须手动设置cookie和session
  • 对于分布式和服务器集群中不方便保证其他系统也能正确解析session

jwt认证

用户的信息通过Token字符串的形式,保存在客户端浏览器中。它同城由三部分组成,分别是Header(头部)、Payload(有效荷载)、Signature(签名),三者之间用 . 分割,即Header.Payload.Signature

  • Payload部分才是真正的用户信息,他是用户信息经过加密之后生成的字符串。
  • Header和Signature是安全性相关的部分,只是为了保证Token的安全性。

使用方式

客户端收到服务器返回的jwt之后,通常会把它保存到localStorage或sessionStorage中,以后,客户端每次与服务器通信,都要带上这个jwt字符串,从而进行身份认证,。

推荐把jwt放在http请求头的Authorzation字段中。

Authorizationn: Bearer <token>

安装jwt相关的包

npm install jsonwebtoken express-jwt
  • jsonwebtoken用于生成jwt字符串
  • express-jwt用于将jwt字符串解析还原成json对象

定义 secret密钥

  • 当生成jwt字符串的时候,需要使用secret密钥对用户信息进行加,最终得到加缪好的jet字符串。
  • 当把jwt字符串解析还原成json对象的时候,需要使用secret进行解密
const express = require('express')
const app = express()
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')

const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }))
app.use(
  express.urlencoded({
    extended: false,
  })
)

// 定义secret密钥,建议将密钥命名为secretKey,值 越复杂越好
const secretKey = 'wxl ^-^'

//将JWT字符串再还原为json对象,一定要在路由之前配置
// expressJWT({secret:secretKey 用来解析Token中间件
// unless({path:[/^\/api\//]})) 用来指定哪些接口不需要访问权限
//然后jwt就会把解析出来的用户信息,挂载到 req.user属性上了,req.user是固定写法,
app.use(expressJWT({ secret: secretKey, algorithms: ['HS256'] }).unless({ path: [/^\/api\//] }))

app.post('/api/login', (req, res) => {
  if (req.body.username !== 'admin' || req.body.password !== '123456') {
    return res.send({ status: 400, message: '登录失败' })
  }
  //调用jwt.sign() 生成JWT字符串, 三个参数分别是:用户信息 ,加密密钥 ,配置对象
  //注意:千万不要把密码加密到token字符中
  const tokenStr = jwt.sign({ username: req.body.username }, secretKey, {
    //   token设置为30s过期
    expiresIn: '30s',
  })
  res.send({
    status: 200,
    message: '登录成功',
    token: tokenStr,
  })
})

//有权限的api接口
app.get('/admin/getinfo', (req, res) => {
  console.log(req.user)
  res.send({
    status: 200,
    message: '获取用户信息成功',
    data: req.user, //
  })
})

app.use((err,req,res,next)=>{
    if(err.name === 'UnauthorizedError'){
        //这次错误是由token解析失败导致的
        return res.send({
            status:401,
            message:'无效的token'
        })
    }
    res.send({
        status:500,
        message:'未知错误'
    })
})
app.listen(80, () => {
  console.log('server running at http://127.0.0.1')
})

以上token生成机制是对称密钥(HS256算法),也就是说不管是颁发模块和其他使用模块都需要同一个key来操作数据,缺点明显.

使用非对称密钥(RS256算法)的话就可以解决,我们 只需要给颁发模块一个私钥负责生成token,其他模块系统通过公钥来验证token解析数据,即可.

步骤:

  1. 终端输入openssl 并回车
  2. 继续输入genrsa -out private.key 1024 回车生成私钥
  3. 必须通过私钥再生成公钥(因为这样才能通过公钥来验证私钥),所以接下来继续
  4. 输入rsa -in private.key -pubout -out public.key 回车生成公钥
  5. 在代码文件里的生成token中的签名位置,放入我们生成的私钥,验证token中的签名位置放入我们生成的公钥.


渣渣龙, 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Node-Express
喜欢 (4)

您必须 登录 才能发表评论!