目录
接着上一篇继续学习node.js
一、express
1、初识express
express类似node.js中http模块,专门用来创建WEB服务器的,相对node中http更加方便、功能更加强大;本质是npm上提供的第三方包。官网地址:Express - 基于 Node.js 平台的 web 应用开发框架 - Express中文文档 | Express中文网
express是基于node.js封装出来的,使用express可以快速创建这两种服务器。
2、安装express
npm i express@4.17.1 //安装指定版本
npm i express //不指定版本直接安装
3、创建并启动web服务器
先新建个express练习目录 express, 里面新建express_test.js
(顺便 npm init -y 初始化一个package.json 文件,后面会用 )
//导入express
const express = require('express')
//创建服务器
const app = express()
//启动服务器
app.listen(3000,()=>{
console.log('服务器启动了')
})
4、监听 GET&POST 请求、响应内容给客户端
//导入express
const express = require('express')
//创建服务器
const app = express()
//监听请求,并向客户端响应数据 (指定user页面)
app.get('/user',(req,res)=>{
res.send('hello express')
})
app.post('/user',(req,res)=>{
res.send({name:'张三',age:18})
})
//启动服务器
app.listen(3000,()=>{
console.log('服务器启动了 http://127.0.0.1:3000')
})
请求中的两个参数:req 请求对象、res 响应对象
用 app.get 监听get请求,app.post 监听post请求
通过res.send() 向客户响应数据
运行结果
5、获取URL中携带的查询参数
可以通过req.query对象查询 如链接:http://127.0.0.1:3000/user?id=1&name=test&str=%E5%AD%97%E7%AC%A6
//监听请求,并向客户端响应数据
app.get('/user',(req,res)=>{
res.send('hello express')
console.log('请求的参数:',req.query)
})
app.post('/user',(req,res)=>{
res.send({name:'张三',age:18})
console.log('请求的参数:',req.query)
})
6、获取URL中动态参数
通过req.params对象访问URL中通过 :匹配到的动态参数
默认req.params是一个空对象, 示例链接:http://127.0.0.1:3000/user/9?id=1&name=test&str=%E5%AD%97%E7%AC%A6
//导入express
const express = require('express')
//创建服务器
const app = express()
//监听请求,并向客户端响应数据
//app.get('/user/:id/:name',(req,res)=>{
app.get('/user/:id',(req,res)=>{
res.send('hello express')
console.log('请求的动态参数参数:',req.params)
})
//启动服务器
app.listen(3000,()=>{
console.log('服务器启动了 http://127.0.0.1:3000')
})
:<key> ,这个key起什么名字,打印里面就会得到什么,比如 :name ,打印就会得到{name:9},
:<key1>/:<key1>参数可以有多个,如 http://127.0.0.1:3000/user/9/test?id=1&name=test&str=%E5%AD%97%E7%AC%A6
7、静态资源托管
express 提供了express.static(),可以方便地创建一个静态资源服务器,如:可以通过代码将图片、css、js等文件对外开放访问
app.use(express.static('public'))
express在指定静态目录下查找文件,并提供对外访问路径,但存放静态文件的目录名不会出现在url中。
如下面示例中托管images和other文件夹后,访问里面的文件
images文件夹中图片:http://localhost:3000/1.png
other文件夹中css文件:http://localhost:3000/reset.css
(查找顺序是按照代码中写的顺序进行查找文件)
const express = require('express')
const app = express()
//托管images文件夹
app.use(express.static('./images'))
//如果托管多个就继续添加,如:托管other目录
app.use(express.static('./other'))
app.listen(3000, () => {
console.log('Server is running here: http://localhost:3000')
})
挂载路径前缀
如果想要再访问的路径前有个前缀,则可以通过代码设置,访问地址http://localhost:3000/public/reset.css
//app.use('/前缀名字自己起',express.static('要托管的资源'))
app.use('/public',express.static('./other'))
二、工具nodemon
每次修改代码都需要重启服务,nodemon工具会监听代码变化,当代码有修改保存会自动重启,不用手动重启项目
1、安装nodemon
npm i -g nodemon
2、执行
//安装nodemon 之前执行方式 :node node.js
//安装后
nodemon node.js
三、express路由
1、express中路由
客户端的请求与服务器处理函数之间的映射关系
//app.请求类型(请求url,处理函数)
//app.METHOD(PATH,HANDLER)
app.get('/', (req, res) => {
res.send('hello word')
})
2、路由的匹配
每一个请求到达服务器,都需要先经过路由匹配,只有匹配成功才会调用对应的处理函数;
匹配时会按照路由顺序进行匹配,如果请求的类型和URL同时匹配成功,express才会将这次请求转给对应的function进行处理
3、路由模块化
为了方便对路由进行模块化管理,express不建议将路由直接挂载到app上,推荐抽离为单独的模块。
①、新建路由文件router.js
//express/router.js
const express = require('express')
//导入路由对象
const router = express.Router()
//挂载具体路由
router.get('/user/list', (req, res) => {
res.send('get user list')
})
router.post('/user/add', (req, res) => {
res.send('add user')
})
//导出路由
module.exports = router
②、注册并使用路由模块
//express/express_test.js
const express = require('express')
const useRouter = require('./router')//引入路由文件
const app = express()
//注册路由
app.use(useRouter)
app.listen(3000, () => {
console.log('Server is running http://localhost:3000')
})
app.use() 这个函数作用是用来注册全局中间件
4、路由模块添加前缀
和静态资源托管类似(在一大类7小条中),添加一个统一访问前缀
//如添加统一前缀 api
// 把 ② 中代码 app.use(useRouter) 变更如下
app.use('/api',useRouter)
四、中间件
1、express中间件
2、express中间件调用流程
当一个请求到达express服务器后,可以连续调用多个中间件,从而对这次请求进行预处理
3、中间件格式
本质上是function处理函数
中间件函数的形参列表中,必须包含 next 参数,而路由处理函数中 只包含req、res。
next函数的作用:是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由
当前中间件处理完了调用next() 后就会转交给下一个中间件
4、创建并使用中间件函数
全局生效中间件
客户端发起请求到达服务器之后,都会触发的中间件,叫全局生效的中间件。
使用app.use(中间件函数) ,即自定义一个全局生效的中间件
const express = require('express')
const app = express()
//自定义一个中间件 并注册使用
// const mn = function(req,res,next){
// console.log('定义一个中间件')
// next()
// }
// //注册使用中间件
// app.use(mn)
//或者直接简化
app.use(function(req,res,next){
console.log('定义一个中间件')
next()
})
app.listen(3000, () => {
console.log('Server is running http://localhost:3000')
})
如果定义多个全局中间件,会按照中间件定义先后顺序依次进行调用。
局部生效中间件
不使用app.use()定义的中间件叫局部中间件;只对某个路由生效的中间件,不会影响其他路由。
局部中间件可以写多个或单个,多个用逗号隔开(多个也可以是数组形式)。
app.get('/',mn1, mn2,mn3,(req, res) => { res.send('Hello World') })
app.get('/',[mn1, mn2,mn3],(req, res) => { res.send('Hello World') })
//express\express_test.js
const express = require('express')
const app = express()
//定义一个局部中间件
const mn = function(req, res, next) {
console.log('这是局部中间件')
next()
}
//局部生效的中间件
//app.get(URL,局部中间件参数, (req, res) => {
//定义一个get路由
app.get('/',mn, (req, res) => {
res.send('Hello World1')
})
app.post('/',(req, res) => {
res.send('Hello World2')
})
app.listen(3000, () => {
console.log('Server is running http://localhost:3000')
})
当访问get路由时会执行打印,访问post路由时不会执行。
5、中间件作用
多个中间件之间共享一个 req 和 res, 这样可以在上游的中间件中统一为 req和res 添加自定义属性或方法,供下游中间件或路由使用
如:在中间件中获取当前时间,把获取的时间挂到req的startTime属性上,在路由中也可以获取到这个时间。
中间件注意事项:
1、必须在路由之前注册中间件
2、执行完中间件代码后,一定不要忘记调用 next() 函数
3、防止代码混乱,调佣next()函数后不要在写额外代码
4、错误级别中间件必须注册在所有路由之后
6、中间件分类
express.json & POST请求数据的接收
如果是post请求,可以使用 req.body 这个属性,来接收客户端发送过来的请求体数据。
默认情况下,如果不配置解析表单数据的中间件,则 req.body 默认等于 undefined。
如果不使用express.json()、express.urlencoded()可以使用三方的一个中间件:body-parser 来解析请求体数据
注:express内置的中间件express.urlencoded 就是基于 body-parser 这个三方中间件进一步封装出来的
const express = require('express')
const app = express()
//解析json格式的请求数据
app.use(express.json())
//解析表单数据和 url-encoded 格式的请求数据
app.use(express.urlencoded())
app.post('/',(req, res) => {
console.log(req.body)
res.send({"name":"John"})
})
app.listen(3000, () => {
console.log('Server is running http://localhost:3000')
})
//1.导入解析表单数据的中间件 body-parserconst
const parser = require('body-parser')
//2.使用 app.use()注册中间件
app.use(parser.urlencoded({extended: false})
7、自定义中间件
const express = require('express');
const app = express()
const qs = require('querystring')//node内置模块 解析和格式化网址查询字符串
app.use((req,res,next)=>{
let str = ''
req.on('data',(chunk)=>{
console.log('data事件====',chunk)
str += chunk
})
req.on('end',()=>{
console.log('end事件====',str)
console.log(qs.parse(str)) //解析出传递数据
next()//调用next()函数
//注意要放在req.on('end', ...) 内部的回调函数中,需要在数据完全接收并解析后再继续处理请求
})
})
app.post('/user',(req,res)=>{
res.send(req.body)//响应给客户端接收到的数据
})
app.listen(3000,()=>{
console.log('server is running http://localhost:3000')
})
使用postman发起post请求并传递4个参数,运行结果如下,返回结果可以使用node内置模块querystring进行把字符串解析成json
如果要实现自定义中间件模块化,实现步骤类似express路由模块化类似,这里就不写具体步骤了~
五、Express 编写接口
准备工作 :新建apiRouter.js
目录结构
G:\myproject\express
express
|---- express_test.js //运行主页
|---- apiRouter.js // 路由模块文件
1、编写get 、post 方式接口
①、创建一个基本服务
②、创建API路由模块apiRouter.js并导出
③、编写 GET & POST 接口逻辑
④、导入路由模块
//apiRouter.js
//导入express、router
const express = require('express')
const router = express.Router()
//定义get方式请求 接口名为bookname
router.get('/bookname',(req,res)=>{
//通过req.query获取get请求参数
const query = req.query
//向客户端响应处理结果
res.send({
status:200,//200表示成功 204表示失败
msg:'GET请求成功',
data:query,//返回给客户端的数据, 这里示例直接把客户端传过来的数据返回给用户
})
})
//定义post方式请求 接口名为 shopname
router.post('/shopname',(req,res)=>{
//通过req.body获取post请求参数
const body = req.body
//向客户端响应处理结果
res.send({
status:200,//200表示成功 204表示失败
msg:'POST请求成功',
data:body,//返回给客户端的数据
})
})
//导出路由
module.exports = router
// express_test.js
//express 编写接口
const express = require('express')
const app = express()
//========此端 是jsonp 接口例子 ============ 配置jsonp 接口 必须在cors之前调用 (jsonp 是get的)=====================
app.get('/api/jsonp',(req,res)=>{
//接收客户端发送的回调函数名称
const funcName = req.query.callback
//要发送给客户端的数据
const data = {name:'测试jsonp',age:18}
//拼接一个完整的字符串
const result = `${funcName}(${JSON.stringify(data)})`
//将拼接好的字符串返回给客户端
res.send(result)
})
//==================== 配置jsonp 接口 end =====================
//=========== 此两行是解决跨域问题的 =================
const cors = require('cors')
//配置解决跨域问题 注意:必须在注册路由之前调用
app.use(cors())
//============此两行是解决跨域问题的 end =============
//配置解析表单数据的中间件 记得放在注册路由之前 (post 时会用)
app.use(express.urlencoded({extended:false}))
//导入路由模块
const useRouter = require('./apiRouter')
//注册路由,并设置统一前缀,名为api
app.use('/api',useRouter)
app.listen(3000,()=>{
console.log('服务器启动成功 http://127.0.0.1:3000')
})
启动服务(node express_test.js , 或者如果安装了nodemon工具 nodemon express_test.js),通过浏览器或者postman访问 http://127.0.0.1:3000/api/bookname?id=12&name=%E8%A5%BF%E6%B8%B8%E8%AE%B0&english=xiyouji&price=888
2、cors 解决跨域
和js同级新建一个index.html,接口用刚刚写好的试验会发现有跨域问题
解决接口跨域主要有两种:
1、CORS (主流的解决方案)
2、JSONP (有缺陷的解决方案:只支持 GET 请求)
cors 是 Express 的一个三方中间件,安装配置cors可解决跨域问题
①、npm i cors 安装中间件
②、const cors = require('cors') 导入中间件
③、在路由前导入配置中间件 app.use(cors())
此时在运行html,点击按钮请求接口就可以成功返回数据
3、关于 cors
CORS (Cross-Origin Resource Sharing,跨域资源共享)由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源。
浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头就可以解除浏览器端的跨域访问限制。
注意:
①、CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。
②、CORS 在浏览器中有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。
cors 响应头
Access-Control-Allow-Origin
//格式
//<origin> 参数的值指定了 允许访问该资源的外域URL
Access-Control-Allow-Origin:<origin> | *
//通配符 *,允许所有的域名访问
res.setHeader('Access-Control-Allow-0rigin','*')
//只允许 来自 https://test.com 域名下的访问
res.setHeader('Access-Control-Allow-0rigin','https://test.com')
Access-Control-Allow-Headers
默认情况下cors仅支持 客户端向服务器发送如下9个请求头:
Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、 Width、Content-Type ( 值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers 对额外的请求头进行声明,否则这次请求会失败!
//允许客户端额外向服务器发送Content-Type 请求头和 X-Custom-Header 请求头
//注意:多个请求头之间使用英文的逗号进行分割
//如额外声明了 Content-Type 和 Custom-Header 两个请求头
res.setHeader('Access-Control-Allow-Headers','Content-Type,Custom-Header')
Access-Control-Allow-Methods
默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。
如果客户端希望通过 PUT、DELETE等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods来指明实际请求所允许使用的 HTTP 方法。
//只允许 POST, GET, DELETE, HEAD 请求方法
res.setHeader('Access-Control-Allow-Methods','POST, GET, DELETE, HEAD')
//允许所有请求方法
res.setHeader('Access-Control-Allow-Methods','*')
cors请求分类
根据请求方式和请求头的不同可以分为2大类:简单请求 和 预检请求
简单请求(同时满足以下2点):
①、请求方式是GET、POST、HEAD三者之一
②、HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type(只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
预检请求(符合以其中1点):
①、请求方式为GET、POST、HEAD之外的请求 Method类型
②、请求头中包含自定义头部字段
③、向服务器发送了 application/json 格式的数据
预检请求:在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这-次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
二者区别:
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求。
预检请求示例
//在上面apiRouter.js 中添加如下代码
//定义delete方式请求
router.delete('/delete',(req,res)=>{
//通过req.query获取get请求参数
const query = req.query
//向客户端响应处理结果
res.send({
status:200,//200表示成功 204表示失败
msg:'DELETE请求成功',
data:query,//返回给客户端的数据
})
})
//html 中添加如下测试代码
<div class="post" onclick="delet()">请求delet接口测试</div>
function delet(){
$.ajax({
type:"delete",
url:"http://127.0.0.1:3000/api/delete?id=12",
success:function(data){
console.log('delete接口请求返回',data);
}
})
}
启动服务,分别点击按钮,查看控制台中的网络,点击get或者post按钮都只有1次,但是delete 会有2次
4、JSONP
浏览器端通过 <script>标签的 src 属性,请求服务器上的数据,同时,服务器返回一个函数的调用。这种请求数据的方式叫做 JSONP。
JSONP特点:
① JSONP 不属于真正的 Ajax 请求,因为它没有使用 XMLHttpRequest 这个对象
② JSONP 仅支持 GET 请求,不支持 POST、PUT、DELETE 等请求
注意:如果项目中已经配置了 CORS 跨域资源共享,为了防止冲突,必须在配置 CORS 中间件之前声明 JSONP 的接口。否则JSONP 接口会被处理成开启了CORS的接口。
//优先创建 JSONP 接口
app.get('/api/jsonp',(req,res)=>{ }) //这个接口不会被处理成 CORS 接口
// 再配置CORS中间件
//这个后面的所有接口,都会被处理成 CORS 接口
app.use(cors())
app.get('/api/get',(req,res)=>{})//比如这个就是开启了CORS 的接口
//express_test.js
//jsonp接口
//具体代码在 五 中第1小节代码中有标注
//html 中继续添加
<div class="post" onclick="jsonp()">请求jsonp接口测试</div>
function jsonp(){
$.ajax({
type:"get",
url:"http://127.0.0.1:3000/api/jsonp",
dataType:"jsonp",
success:function(data){
console.log('jsonp接口请求返回',data);
}
})
}
5、express中使用session
Cookie 是存储在用户浏览器中的一段不超过4KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。
不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器
Cookie特性:自动发送、域名独立、过期时限、4KB限制、不具安全性express提供三方中间件 express-session
npm i express-session //安装
引入并注册,具体使用字段可查询官网说明:https://www.npmjs.com/package/express-session
//express_test.js
//继续在 express_test.js 文件中导入session并全局注册
const session = require('express-session')
app.use(session({
secret:'keyboard cat',
resave:false,
saveUninitialized:true
})
)
6、express中使用 JWT生成token
安装jsonwebtoken、express-jwt
jsonwebtoken 用于生成 JWT 字符串
express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
npm install jsonwebtoken express-jwt
我这里遇到一个问题 安装完express-jwt后下面引用时一直报错 expressJWT is not a function 默认安的8+版本,按照官网的方式修改引入也报错,后来降低版本可以
项目中导入包
//express_test.js
const jwt = require('jsonwebtoken') //导入生成jwt字符串的包
const expressJWT = require('express-jwt')//解析由jwt生成字符串的包
const secretKey = 'test password' //定义一个秘钥,值是一个字符串
定义secret秘钥
了保证 JWT 字符串的安全性,防止 JWT 字符串在网络传输过程中被别人破解,定义一个用于加密和解密的 secret 密钥:
① 当生成 JWT 字符串的时候,需要使用 secret 密钥对用户的信息进行加密,最终得到加密好的JWT 字符串② 当把 JWT 字符串解析还原成 JSON 对象的时候,需要使用 secret 密钥进行解密
写一个登录和用户信息接口
//express_test.js
const express = require('express')
const app = express()
const cors = require('cors')//解决跨域
const session = require('express-session')//保存session
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
const secretKey = 'test password' //定义一个秘钥,值是一个字符串
//配置解决跨域问题 注意:必须在注册路由之前调用
app.use(cors())
//配置解析表单数据的中间件 记得放在注册路由之前
app.use(express.urlencoded({extended:false}))
//unless 指定带有api的接口不需要访问权限
app.use(expressJWT({secret:secretKey}).unless({ path: [/^\/api\//] }) ) //注册解密token的中间件
//注册 session 全局中间件
app.use(session({
secret:secretKey,
resave:false,
saveUninitialized:true
}) )
//登录接口
app.post('/api/login',(req,res)=>{
if(req.body.username === 'admin' && req.body.password === '123456'){
const userInfo = req.body
req.session.userInfo =userInfo
req.session.isLogin = true
//expiresIn 设置token有效时间
const token = jwt.sign({username:userInfo.name},secretKey,{expiresIn:'50s'}) //对用户信息进行加密
res.send({
status:200,
msg:'登录成功',
data:{
token:token,
...req.body,
}
})
}else{
res.send({
status:204,
msg:'用户名或密码错误',
data:req.body
})
}
})
//获取用户信息接口
app.post('/user',(req,res)=>{
if(req.session.isLogin){
res.send({
status:200,
msg:'获取用户信息成功',
data:req.session.userInfo
})
}else{
res.send({
status:204,
msg:'获取用户信息失败',
data:req.session.userInfo
})
}
})
//
app.listen(3000,()=>{
console.log('服务器启动成功: http://127.0.0.1:3000')
})
注意:获取用信息时,请求头的key是 authorization 值 一定要加上 Bearer 空格后加上token