koa入门
简介
一个极简的流式nodejs服务器框架
何为流式
把服务器请求生命流程理解为一条线
------------------
假如我们的API为生成证书, 用户请求一次, 服务器生成一次证书, 处理函数叫A
生命流程变为
---------A--------
假设我们要在生成证书前要先校验参数, 校验参数方法叫B
---B------A--------
然后我们加个统计API的执行时机, 处理的方法叫C
这个时机点的计算方式是 API返回的时间戳 - 进入API的时间戳
C---B------A--------C
koa-router
koa本身主要处理http相关的事情, 没有处理路由相关的事情
路由主要交给koa-router
这个组件
koa-router主要使用方式
const Koa = require('koa')
const app = new Koa()
const router = require('koa-router')()
router.get(url, middleware, middleware...)
app.use(router.routes(), router.allowedMethods())
middleware
为一个函数
以记录API处理事件为例写一个middleware
async function (ctx, next) {
const startTime = new Date().getTime()
await next()
const handleApiTime = new Date().getTime() - startTime
}
实战应用
还是以我生成证书为例
const Koa = require('koa')
const app = new Koa()
const router = require('koa-router')()
const generateCa = require('./controller/generate-ca/index')
router.get(
'/generate-ca', async (ctx, next) => {
await generateCa(ctx, next)
await next()
}
)
app.use(router.routes(), router.allowedMethods())
app.listen(3000)
generateCa主要流程就是生产证书的代码, 这里就贴下返回的数据就好了
// ./controller/generate-ca/index
module.exports = async function (ctx, next) {
// ...
ctx.body = {
code: 0,
msg: '',
data: { name }
}
await next()
}
这样 我的一个生成证书服务就基本搭起来了
验证参数中间件
但是假设我们要校验请求参数中的dns0
和dns1
, 需要添加一个校验参数的中间件validate-param.js
const ruleCheckMap = {
notEmpty: function(key, value){
if (!value){
throw new Error(`${key} 不能为空`)
}
},
dns: function(key, value){
// 只保证无特殊字符
if (!value){
} else if (!(/^[a-zA-Z0-9\.\-\*]{5,50}$/.test(value))){
throw new Error(`${key} 不合法, 只允许字母数字以及-_.等字符`)
}
}
}
module.exports = function (param){
return async (ctx, next) => {
const { rules } = param
for (let index = 0; index < rules.length; index++) {
const { descriptor, key } = rules[index]
const value = ctx.query[key]
for (let descriptorIndex = 0; descriptorIndex < descriptor.length; descriptorIndex++) {
const type = descriptor[descriptorIndex];
ruleCheckMap[type](key, value)
}
}
await next()
}
}
这是一个高阶函数, 接受一定的参数配置, 然后返回配置化的参数校验中间件
这个中间件就可以被业务使用
router.get(
'/generate-ca',
validateParam({
rules: [
// dns0必须并且符合dns规则, dns1可以空或者有的话需要符合dns规则
{ key: 'dns0', descriptor: ['notEmpty','dns'] },
{ key: 'dns1', descriptor: ['dns'] }
]
}),
async (ctx, next) => {
await generateCa(ctx, next)
}
)
添加队列处理
队列中间件
const queue = require('queue')
const queueObj = {}
module.exports = function(options){
return async function(ctx, next ){
const { key, max, msg } = options
if (!queueObj[key]) {
queueObj[key] = queue({
autostart: true,
concurrency: max
})
}
let promise
if (queueObj[key].length >= max ){
throw new Error(msg)
} else {
promise = next()
queueObj[key].push(async function(cb){
await promise
cb()
})
}
return promise
}
}
使用业务使用队列中间件
router.get(
'/generate-ca',
validateParam({
rules: [
{ key: 'dns0', descriptor: ['notEmpty','dns'] },
{ key: 'dns1', descriptor: ['dns'] }
]
}),
// 添加队列key 和执行max队列最大处理数
queue({
key: 'generate-ca',
max: 5,
msg: '抱歉, 当前生成证书请求过多, 请稍候再试'
}), async (ctx, next) => {
await generateCa(ctx, next)
}
)
异常错误
由上面的中间件遇到异常都是直接thorw new Error
的, 所以这里需要对错误进行一个统一的处理
app.use(async function(ctx, next){
try {
await next()
} catch(err) {
ctx.body = { code: -1, msg: err.message }
}
})
// response
app.use(router.routes(), router.allowedMethods())
这里没有定义为koa-router
的middleware, 而是koa
的middleware
这里错误处理函数需要定义在koa-router之前, 因为错误异常函数要把所有业务代码给try-catch
住