Node-Express

4个月前 (01-29) 678次浏览 已收录 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)=>{
    
})
  • req:请求对象(包含了与请求相关的属性和方法
  • res:响应对象(包含了与响应相关的属性和方法

监听POST请求

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

})

把内容响应给客户端

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

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

// 把内容响应给客户端
app.get('/user',(req,res)=>{
    res.send({name:'wxl'})
})
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。

app.get('/',(req,res,next)=>{
    next()
})

next函数的作用

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

定义中间件函数

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

全局生效的中间件

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

通过调用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)=>{
    throw new Error('人为制造一个自定义错误')
})

//错误级别的中间件必须注册在所有路由之后
app.use((err,req,res,next)=>{
    console.log('发生了错误',err.message)
    res.send('error'+err.message)
})

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

/**
 * 发生了错误 人为制造一个自定义错误
 */
/**
 * 客户端:
 * error人为制造一个自定义错误
 */
除了错误级别的中间件,其他的中间件必须在路由之前进行配置

Express内置的中间件

  • express.static快速托管静态资源的内置中间件
  • express.json 解析json个格式的请求体数据
  • express.urlencoded解析url-encoded格式的请求体数据

固定配置格式

//配置解析application/json格式数据的内置中间件
app.use(express.json())
//配置解析application/x-www-form-urlencoded格式数据的内置中间件
app.use(express.urlencoded({extended:false}))
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' }
 */

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认证的缺点

session认证机制需要配合cookie才能实现,由于cookie默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外配置,才能实现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')
})


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

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