Koa
npm install koa
请求
写的好的博客:https://www.jianshu.com/p/e6aec8bcdcf4
Context
Koa Context 将节点的请求和响应对象封装到单个对象中,该对象为编写Web应用程序和API提供了许多有用的方法
每个请求都会创建一个Context
,并在中间件中作为接收者或ctx标识符引用
app.use(async ctx => {
ctx; // is the Context
ctx.request // is a Koa Request
ctx.response // is a Koa Response
})
为方便起见,许多上下文的访问器和方法与委托给它们的ctx.request
或ctx.response
是相同的
如:ctx.status
等同于 ctx.response.status
// ctx意思为context(上下文),拥有请求和响应的信息,大概内容如下
{
request: {
method: 'GET',
url: '/',
header: {
host: 'localhost:3000',
connection: 'keep-alive',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36',
'sec-fetch-dest': 'document',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'sec-fetch-site': 'none',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9'
}
},
response: {
status: 200,
message: 'OK',
header: [Object: null prototype] {
'content-type': 'text/plain; charset=utf-8',
'content-length': '6'
}
},
app: { subdomainOffset: 2, proxy: false, env: 'development' },
originalUrl: '/',
req: '<original node req>',
res: '<original node res>',
socket: '<original node socket>'
}
ctx.request
和 ctx.req
的区别(res同理):
-
ctx.request
是koa2中context经过封装的请求对象,它用起来更直观和简单 -
ctx.req
是context提供的node.js原生HTTP请求对象,虽然不那么直观,但是可以得到更多的内容,更适合深度编程
API
- ctx.req
Node的请求对象
-
ctx.res
Node的响应对象
-
ctx.request
Koa的请求对象
-
ctx.response
Koa的响应对象
-
ctx.method
获取请求的类型
-
ctx.url
获取请求的地址
路由
koa和express不同的是,koa要使用路由时,需要额外引入包
npm install koa-router
var Koa = require('koa')
var app = new Koa()
// 引入时直接创建实例 等同于var Router = require('koa-router') ; var router = new Router()
var router = require('koa-router')()
// 配置路由
router.get('/',async (ctx) => {
// 返回数据,相当于res.writeHead() 和 res.end()
ctx.body = '首页'
console.log(ctx)
}).get('/news', async (ctx) => {
ctx.body = '新闻页'
})
// 链式调用写法,也可以再 router.get(),下面的app.use同理
// 启动路由(让路由生效)
app.use(router.routes()) // 启动路由 必须加上才能正常使用路由
// 可以配置也可以不配置,建议配置,作用:用在router.routes()之后,所以在当所有路由中间件最后调用,此时根据ctx.status设置response响应头
.use(router.allowedMethods())
app.listen(3000)
其它使用技巧
import { Context } from 'koa'
import Router from 'koa-router'
const router = new Router({
prefix: '/api', // prefix 表示默认在所有路由前加的前缀
})
// 如声明一个router.get('/login') 实际声明的是'/api/login',用prefix可以省略
获取请求内容
get
在ctx里获取
- query:返回将字符串格式化好的json对象
- querystring:返回字符串
// 请求url: /admin?ad=123&ac=321
app.use(async(ctx)=>{
let url =ctx.url
//从request中获取GET请求
let request =ctx.request
let req_query = request.query
let req_querystring = request.querystring
//从上下文中直接获取
let ctx_query = ctx.query // { ad: '123', ac: '321' }
let ctx_querystring = ctx.querystring // ad=123&ac=321
})
// 使用 ctx.xxx 和 ctx.request.xxx 功能完全一样
上面的方法实际使用发现问题,使用下面这个
ctx.params // { ad: '123', ac: '321' }
post
对于POST请求的处理,koa2没有封装方便的获取参数的方法,需要通过解析上下文context中的原生node.js请求对象req来获取
获取POST请求的步骤:
1、解析上下文ctx中的原生nodex.js对象req。
2、将POST表单数据解析成query string-字符串(如:name=zyb&age23)。
3、将字符串转换成JSON格式
动态路由
在声明匹配路由路径时用:
声明跟在这之后的url为指定变量,并且可以被匹配到
通过ctx.params
获取到动态路由的传值
router.get('/admin/:aid',async (ctx) => {
console.log(ctx.params)
}
// 请求/admin/test1,打印{ aid: 'test1' }
// 请求/admin/test2,打印{ aid: 'test2' }
中间件
koa的中间件和express不同,express的中间件执行的顺序是只看声明的顺序
koa中间件的执行顺序:匹配到没有next()
返回去重新执行一遍之前匹配过的中间件,同路径的路由或中间件看声明顺序执行
匹配中间件→匹配路由→重新回去匹配中间件或路由执行next()
后的代码
声明路径为 /
(省略路径默认 /
)时,表示匹配所有中间件
const Koa = require("koa");
const app = new Koa;
app.use(async (ctx, next) => {
console.log('1');
await next(); // 调用下一个middleware
console.log('5')
});
app.use(async (ctx, next) => {
console.log('2');
await next();
console.log('4');
});
app.use(async (ctx, next) => {
console.log('3');
});
app.listen(3000)
console.log("listening on port 3000")
// 执行顺序:1 2 3 4 5
const Koa = require("koa");
const app = new Koa;
var router = require('koa-router')()
router.get('/app', async (ctx,next) => {
ctx.body = 'app'
console.log('3')
await next()
console.log('5')
})
app.use(async (ctx, next) => {
console.log('1');
await next();
console.log('7')
});
app.use(async (ctx, next) => {
console.log('2');
await next();
console.log('6')
});
router.get('/app', async (ctx) => {
console.log('4');
});
router.get('/app', async (ctx) => {
console.log('因为没有next因此无法匹配到这');
});
app.use(router.routes())
app.listen(3000)
// 访问 /app时,执行顺序 1 2 3 4 5 6 7
// 可以看到,是先匹配完了能匹配的中间件才开始去匹配路由的
错误处理中间件:
app.use(async (ctx,next) => {
next() // 也可以写在if判断后
if(ctx.status == 404) {
ctx.body('404页面')
}
else {
.....
}
})
ejs模板
npm istall koa-views
npm install esj
在koa中使用ejs并不需要安装了还要引用,只需下载就行,配置koa使用的是koa-views
同时配置该中间件时,必须保证在所有路由前配置,即app.use
配置方法一:网页文件的后缀名为.ejs
const views = require('koa-views');
//views 代表引入的koa-views模块;template代表我们保存模板文件的目录
app.use(views('views', {
extension: 'ejs' //指定我们使用的模板为ejs
})
);
配置方法二:网页文件的后缀为.html
app.use(views('views', {
map: {
html: 'ejs'
}
}))
const Koa = require("koa");
const app = new Koa;
var router = require('koa-router')()
const views = require('koa-views')
app.use(views('views', {
extension: 'ejs'
}))
router.get('/',async (ctx) => {
// index.ejs 需要放在项目/views文件夹下才能找到, awit必须加上
await ctx.render('index', {
title: "标题"
})
})
app.use(router.routes())
app.listen(3000)
// index.ejs
<body>
<%= title %>
</body>
坑
使用TS编写时,ctx的返回类型为Context
,但是此时如果使用import
方式来导入koa-router
,将ctx声明为Context
时会因为有点问题报错,使用require
的方式来导入就不会出现此问题,待找原因
即koa-views
和koa-router
同时用时,使用require
方式引入路由
获取请求数据
原生NodeJs获取提交数据方式
【连接上面ejs代码】由于这里是异步渲染ejs,因此获取页面数据时也应该异步获取(用promise)
这种方式很麻烦
const Koa = require("koa");
const app = new Koa;
var router = require('koa-router')()
const views = require('koa-views')
const getPostData = function(ctx) {
return new Promise(function(resolve,reject){
try {
let str = ''
ctx.req.on('data', function(chunk) {
str += chunk
})
ctx.req.on('end', function(chunk) {
resolve(str)
})
}
catch(err) {
reject(err)
}
})
}
// 在/doAdd路由里获取传送过来的post数据{name:...}
router.post('/doAdd', async(ctx) => {
// 原生nodejs在koa中获取提交的数据
var data = await getPostData(ctx)
ctx.body = data
})
app.use(views('views', {
extension: 'ejs'
}))
router.get('/',async (ctx) => {
await ctx.render('index')
})
app.use(router.routes())
app.listen(3000)
使用koa中间件获取提交数据方式
npm install koa-bodyparser
var bodyParser = require('koa-bodyparser')
app.use(bodyParser()) // 配置中间件
ctx.request.body // 获取表单提交数据
const Koa = require("koa");
const app = new Koa;
var router = require('koa-router')()
const views = require('koa-views')
var bodyParser = require('koa-bodyparser')
router.post('/doAdd', async(ctx) => {
ctx.body = ctx.request.body
})
app.use(views('views', {
extension: 'ejs'
}))
router.get('/',async (ctx) => {
await ctx.render('index')
})
app.use(bodyParser()) // 注意要放在路由中间件前,否则提交表单不会自动跳转到提交页面
app.use(router.routes())
app.listen(3000)
静态资源
去指定目录寻找静态资源文件,如果能找到返回对应文件,否则next()
npm install koa-static
const static = require('koa-static')
//app.use(static(dir_name))
app.use(static('static')) // 匹配/static目录下的静态资源
app.use(static('public')) // 可以配置多个,去多个目录下寻找
// 这里是绝对路径,且省略第一个/
// 即如果要匹配/src/style下的内容时,应该是下面这个,写成/src/style不行
app.use(static('src/style'))
// 这里有个坑,如:html需要成功导入一个/static/css/index.css的静态资源文件,由于中间件已经默认去/static文件夹下寻找了,因此路径需要省略static,应该用第二种写法
<link rel="stylesheet" href="static/css/index.css">
<link rel="stylesheet" href="css/index.css">
认证机制
Cookie
cookie介绍
-
保存在浏览器客户端
-
用同一个浏览器访问同一个域名的时候共享数据
浏览器与WEB服务器之间是使用HTTP协议进行通信的,当某个用户发出页面请求时,WEB服务器只是简单的进行响应,然后就关闭与该用户的连接,因此当一个请求发送到WEB服务器时,无论其是否是第一次来访,服务器都会把它当作第一次来对待,每一次的访问是没有建立联系的(http的无状态)
再再一次请求某个网站的链接时,将本地的cookie一并发送过去,这样浏览器就能识别到还是同一个客户在操作(实现多个页面之间数据的传递)
ctx.cookies.set(name, value, [options])
options属性:
- maxAge:一个数组表示从
Date.now()
得到的毫秒数,表示相对时间 - expires:cookie过期的Date,表示绝对时间
- path:cookie路径,默认
'/'
都可访问,也可设置具体路由表示只在访问特定路由中传递cookie - domain:cookie域名,默认当前域下面的所有页面都可以访问
- secure:安全cookie,默认false,为true时表示仅https可访问
- httpOnly:是否只是服务器可访问cookie,默认true
- overwrite:是否覆盖以前设置的同名cookie,默认flase
// 正常的基本配置
const Koa = require("koa");
const app = new Koa;
const router = require('koa-router')()
router.get('/',async (ctx) => {
ctx.cookies.set('userinfo','mary',{
maxAge:60*1000*60 // 多少毫秒后过期
})
})
router.get('/news',async (ctx) => {
var userinfo = ctx.cookies.get('userinfo')
console.log(userinfo) // 访问首页再访问/news路由,打印mary
})
app.use(router.routes())
app.listen(3000)
koa中没法直接设置中文的cookie值
console.log(new Buffer('汉字').toSting('base64')) //拿到打印的中文base64字符
console.log(new Buffer('拿到的base64字符', 'base64').toString()) // 还原中文字符
因此在设置的时候使用base64字符,想要取cookie并正常显示的时候转换为汉字即可
Session
session也用来记录客户状态,不同的是cookie保存在客户端浏览器中,而session保存在服务器上
工作流程
session需要依赖cookie:
当浏览器访问服务器并发送第一次请求时,服务器端会创建一个session对象,生成一个类似于key,value的键值对
然后将key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带key(cookie)找到对应的session(value),客户的信息都保存在session中
![](https://i-blog.csdnimg.cn/blog_migrate/b518dd52d2856ace9b41f2bede1bf864.png)
npm install koa-session
// 配置
const session = require('koa-session')
app.keys = ['some secret hurr'] // cookie的签名
const CONFIG = {
// 主要设置maxAge和renew
key: 'koa:sess', // 默认
maxAge: 600000, // 最大过期时间(相对毫秒)
overwrite:true, // 默认 true
httpOnly:true, // 默认true
signed:true, // 签名,默认true
rolling:false, // 每次访问的时候重新注册session,默认false
renew:true // 当用户一直停留在一个页面时,有操作情况下session快过期的时候重新设置,默认flase(以防长时间操作过程中session失效)
}
app.use(session(CONFIG, app))
当设置了signed:true
时,则必须要配置app.key
属性,里面的字符串属性可以随意填,但是一般默认'some secret hurr'
,作用是将cookie的内容通过秘钥进行加密处理
const Koa = require("koa");
const app = new Koa;
const router = require('koa-router')()
const session = require('koa-session')
router.get('/',async (ctx) => {
ctx.session.userinfo = '张三' // 可以直接设置中文
})
router.get('/news',async (ctx) => {
var userinfo = ctx.session.userinfo
console.log(userinfo)
})
app.keys = ['some secret hurr']
const CONFIG = {
key: 'koa:sess',
maxAge: 600000,
overwrite:true,
httpOnly:true,
signed:true,
rolling:false,
renew:true
}
app.use(session(CONFIG, app))
app.use(router.routes())
app.listen(3000)
跨域处理
JSONP
html中,有不受同源策略限制的标签
<script src="...">` //加载js脚本
<img src="..."> //图片
<link href="..."> //css
<iframe src="..."> //任意资源
JSONP就是利用了<script/>
标签的这个性质,利用脚本读取到跨域的数据并加载到本服务器,缺点是只能在GET请求中使用且不安全
CROS
浏览器发出CORS请求,需要对请求头增加一些信息,服务器会根据这些信息来是否决定同意这次请求
CROS需要请求方和服务器同时支持(IE不能低于10版本),几乎所有浏览器都支持CROS,因此只需要在服务器上实现CROS即可
服务器控制CROS的主要头信息字段:
-
Access-Control-Allow-Origin
必须字段,浏览器通过设置该字段表示开启CROS,它指定允许进入来源的域名、ip+端口号 。 如果值是
*
,表示接受任意的域名请求,这个方式不推荐,主要是因为其不安全 -
Access-Control-Allow-Credentials
可选字段,它设置是否可以允许发送cookie,true表示cookie包含在请求中,false则相反,默认为false。如果项目需要cookie就得设置该字段了,CORS请求默认不发送Cookie和HTTP认证信息的,所以在此基础上同时也需要在前端设置(以axios为例):axios.defaults.withCredentials = true
-
Access-Control-Max-Age
可选字段,用于配置CORS缓存时间,即本次请求的有效期,单位为秒
-
Access-Control-Allow-Methods
可选字段,设置允许请求的方法
-
Access-Control-Allow-Headers
可选字段,设置允许的请求头信息
koa处理跨域
koa解决跨域的方式有很多种,最好的方案应该是在服务器端设置支持跨域
-
原生设置方式:在koa2中设置具体的请求头信息
设置允许请求的域名
app.use(async (ctx, next) => {
ctx.set("Access-Control-Allow-Origin", "*") // 允许所有域名请求
await next()
})
// 只允许来自 http://localhost:8080 域名的请求
// ctx.set("Access-Control-Allow-Origin", "http://localhost:8080")
设置允许的跨域请求方式
ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE")
设置服务器所支持的所有头信息字段(不同值用逗号分隔)
ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type")
/*
服务器收到请求,检查
Origin
Access-Control-Request-Method
Access-Control-Request-Headers
字段,确认允许跨源请求,即可做出响应。
*/
设置具体请求中的媒体类型信息
ctx.set("Content-Type", "application/json;charset=utf-8")
设置是否允许发送cookie【可选设置】
ctx.set("Access-Control-Allow-Credentials", true);
// 默认情况下,Cookie不包括在CORS请求之中,当设置成允许请求携带凭证cookie时,需要保证"Access-Control-Allow-Origin"是服务器有的域名,而不能是"*"
设置次验证的有效时间,即在该时间段内服务端可以不用再进行验证【可选设置】
ctx.set("Access-Control-Max-Age", 300); // 单位为s
// 当请求方法是PUT或DELETE等特殊方法或者Content-Type字段的类型是application/json时,服务器会提前发送一次请求进行验证
返回自定义所需字段
CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma
需要获取其他字段时,使用Access-Control-Expose-Headers
ctx.set("Access-Control-Expose-Headers", "myData")
完整的一次请求:
app.use(async (ctx, next) => {
ctx.set("Access-Control-Allow-Origin", "*")
ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type")
ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type")
ctx.set("Content-Type", "application/json;charset=utf-8")
ctx.set("Access-Control-Allow-Credentials", true)
ctx.set("Access-Control-Max-Age", 300)
ctx.set("Access-Control-Expose-Headers", "myData")
await next()
})
- 使用中间件方式:koa2-cors
npm i koa2-cors -s
注册中间件
const cors = require('koa2-cors')
app.use(cors(...))
const Koa = require('koa');
const bodyParser = require('koa-bodyparser'); //post数据处理
const router = require('koa-router')(); //路由模块
const cors = require('koa2-cors'); //跨域处理
const app = new Koa();
app.use(
cors({
origin: function(ctx) { //设置允许来自指定域名请求
if (ctx.url === '/test') {
return '*'; // 允许来自所有域名请求
}
return 'http://localhost:8080'; //只允许http://localhost:8080这个域名的请求
},
maxAge: 5, //指定本次预检请求的有效期,单位为秒。
credentials: true, //是否允许发送Cookie
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], //设置所允许的HTTP请求方法
allowHeaders: ['Content-Type', 'Authorization', 'Accept'], //设置服务器支持的所有头信息字段
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'] //设置获取其他自定义字段
})
);
router.post('/', async function (ctx) {
ctx.body = '请求成功了'
});
app.use(router.routes())
.use(router.allowedMethods());
app.listen(3000);
一般无特殊用法的话直接全部配置默认就可(即什么都不需要填)
app
.use(cors())
.use(bodyParser())
.use(router.routes())