Koa 一款简洁,轻量级的HTTP中间件框架。它本身不包含任何中间件,你可以根据自己业务需求来添加使用中间件,中间件支持链接调用。
中间件是一个函数(异步或者同步)处在 HTTP request(请求)与 HTTP response (响应)之间,用来实现某种中间功能 app.use()
来加载中间件。基本上,Koa 所有功能都是通过中间件来实现的,中间件函数会被传入两个参数:1) ctx context 对象,表示一次对话的上下文(requset和response);2) next 函数,调用 next 函数可以把执行权交给下一个中间件,下一个中间件执行完会把执行权再交回上一个中间件。如果中间件中有异步操作,需要使用 async、await 关键字,将其写成异步函数
Koa开发团队和Express是同一群人,Koa和Express设计思想上很相近。Express开发中你需要写很多函数回调以实现异步编程。但Koa是通过async
方法实现异步编程,在代码整洁和可阅读性上会更好。
Koa核心概念只有:Application(应用程序),Context(请求上下文),Request(http request对象),Response(http response对象)。
Hello World Koa必须运行在node v7.6.0 以上,因为它完全基于async
方法之上。
1 2 3 4 5 6 7 8 9 10 11 12 const Koa = require ('koa' );const app = new Koa(); app.use(async ctx => { ctx.body = 'Hello World!' }); app.listen(3000 )
Application Koa 应用程序是包含了一组中间件函数的对象。Application接受到一个http请求,它会按照中间件之间的编码顺序在栈内依次执行。并且它允许你执行操作向下传递请求,之后过滤并逆序返回响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 const Koa = require ('koa' );const app = new Koa();app.use(async (ctx, next) => { const start = Date .now(); await next(); const ms = Date .now() - start; ctx.set('X-Response-Time' , `${ms} ms` ); }); app.use(async (ctx, next) => { const start = Date .now(); await next(); const ms = Date .now() - start; console .log(`${ctx.method} ${ctx.url} - ${ms} ` ); }); app.use(async ctx => { ctx.body = 'Hello World' ; }); app.listen(3000 );
上面例子中通过app.use()
给应用程序添加三个自定义的中间件。当一个http请求进入时,x-response-time中间件先执行。x-response-time内部调用了await next()
方法,它表示先执行下一个中间件(logger)。logger中间件这时就会被调用,在logger内部也有个await next()
方法。则最后一个response中间件被执行。response执行完后再继续执行logger中间件剩余代码,最后执行x-response-time中间件剩余代码。
app.listen() 创建并启用一个http服务器。
1 2 3 const Koa = require ('koa' );const app = new Koa();app.listen(3000 );
app.use(function) 将一个中间件添加到应用程序中。当有http进入时可能会触发该中间件。
app.context Context的原型。你可以通过编辑app.context为Context添加属性。添加的属性可以在任何中间件中的Context中访问到。
Context 每个请求都会创建一个Context,在中间件中通过参数(ctx)可以获得。Context将node得request和response对象封住在单个对象上,并提供了在HTTP服务器开发中会经常使用到得方法。
1 2 3 4 5 6 7 8 9 10 app.use(async ctx => { ctx; ctx.request; ctx.response; ctx.res; ctx.req; ctx.state; ctx.app; ctx.throw(400 , 'name required' ); });
设置、获取cookie 通过 ctx.cookies.get(name,[options])
可以获取到网络中的cookie。
options
支持的选项:
通过ctx.cookies.set(name,value,[options])
创建cookie。
options
支持的选项:
maxAge 有效期。如果为正数,表示改Cookie会在maxAge毫秒后自动失效如果为负数表示Cookie仅在本浏览器窗口以及子窗口打开的子窗口有效。
signed cookie签名值。
expires 过期时间。// new Date(‘2017-02-15’)
path cookie保存路径。
domain cookie所在域名
httpOnly 是否只用于http请求中获取
overwrite 是否允许重写
secure 是否只能用https传递
Request Koa Request对象是在node 的vanilla请求对象之上的抽象,提供很多在HTTP服务中经常使用的方法
1 2 3 4 5 6 7 8 9 10 request.header request.header= request.method request.url request.origin request.href request.path request.querystring request.host request.type
Response 1 2 3 4 5 6 7 8 9 response.header response.socket response.status response.status= response.message response.message= response.body response.body=
插件 Koa-router 是使用最多的Koa路由中间件。
常用用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var Koa = require ('koa' );var Router = require ('koa-router' );var app = new Koa();var router = new Router(); router.get('/' , (ctx, next ) => { ctx.redirect('/index' ); }); router.get('/index' , (ctx, next ) => { ctx.body="index page!" }); app.use(router.routes()).use(router.allowedMethods()); app.listen(3000 , function ( ) { console .log('listen at http://localhost:' + 3000 ); });
HTTP动词方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 router .get('/' , (ctx, next ) => { ctx.body = 'Hello World!' ; }) .post('/users' , (ctx, next ) => { }) .put('/users/:id' , (ctx, next ) => { }) .del('/users/:id' , (ctx, next ) => { }) .all('/users/:id' , (ctx, next ) => { });
router.all()
匹配所有方法。
路由嵌套 1 2 3 4 5 6 7 8 9 var forums = new Router();var posts = new Router();posts.get('/' , (ctx, next ) => {...}); posts.get('/:pid' , (ctx, next ) => {...}); forums.use('/forums/:fid/posts' , posts.routes(), posts.allowedMethods()); app.use(forums.routes());
URL参数 通过路由参数映射添加到ctx.params
上
1 2 3 4 router.get('/:category/:title' , (ctx, next ) => { console .log(ctx.params); });
指定路由前缀 路由器级别上可以添加路径前缀
1 2 3 4 5 6 var router = new Router({ prefix : '/users' }); router.get('/' , ...); router.get('/:id' , ...);
实践设计 | app.js — 应用入口
| router.js — 路由文件
| controllers — 控制器目录
|login.js — 登录页 控制器
|index.js — 首页 控制器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 const Koa = require ('koa' );const app = new Koa();const router = require ('./router' )app.use(router()); app.listen(3000 ,()=> { console .log("koa service start" ) }) const fs = require ('fs' );const Router = require ('koa-router' );const router = Router();function register (router, rules ) { for (let val of rules) { let paramsArr = [val.path]; if (Array .isArray(val.ctr)) { for (let ctrItem of val.ctr) { paramsArr.push(ctrItem); } } else { paramsArr.push(val.ctr); } router[val.method].apply(router, paramsArr); } } function collectRule (router ) { let files = fs.readdirSync(__dirname+'/controllers' ); let js_files = files.filter((f )=> { return f.endsWith('.js' ); }) for (let f of js_files ) { let rules = require (__dirname +'/controllers/' +f); register(router,rules) } } module .exports = function ( ) { collectRule(router); return router.routes(); } const login = async (ctx,next) => { ctx.body = "Login page!" } module .exports = [ { method : 'get' , path : ['/' ,'/login' ], ctr : login } ] const index = async (ctx,next) => { ctx.body = "Index page!" } module .exports = [ { method : 'get' , path : '/index' , ctr : index } ]
koa-bodyparser会将请求的数据解析到ctx.request.body
上。如果没有数据则是个空对象。
1 2 3 4 5 6 7 8 9 10 11 var Koa = require ('koa' );var bodyParser = require ('koa-bodyparser' );var app = new Koa();app.use(bodyParser()); app.use(async ctx => { ctx.body = ctx.request.body; });
Koa-static 静态资源服务中间件,实现静态文件的访问响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const Koa = require ('koa' );const app = new Koa();const static = require ('koa-static' );const staticPath = './static' ; app.use(static ( path.join( __dirname, staticPath) )) app.use( async ( ctx ) => { ctx.body = 'hello world' }) app.listen(3000 , () => { console .log('[demo] static-use-middleware is starting at port 3000' ) })
Koa-session中间件实现服务器端很重要的session创建读取删除等功能,并能指定session保存的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const session = require ('koa-session' );const Koa = require ('koa' );const app = new Koa();app.keys = ['some secret hurr' ]; const CONFIG = { key : 'koa:sess' , maxAge : 86400000 , overwrite : true , httpOnly : true , signed : true , }; app.use(session(CONFIG, app)); app.use(ctx => { if (ctx.path === '/favicon.ico' ) return ; let n = ctx.session.views || 0 ; ctx.session.views = ++n; ctx.body = n + ' views' ; }); app.listen(3000 ); console .log('listening on port 3000' );
第三方存储 如果需要将 session保存到数据库中(redis、mysql、moongodb),需要在config配置项添加store
属性。store
中需要定义三个方法:
get(key, maxAge, { rolling }) - 通过key获取session
set(key, sess, maxAge, { rolling, changed }) - 设置session
destroy(key) - 销毁session
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 const Koa = require ('koa' );const app = new Koa();const session = require ('koa-session' )app.keys = ['secret' ]; const { promisify } = require ('util' );var redis = require ("redis" ),client = redis.createClient(); const getAsync = promisify(client.get).bind(client);client.on('ready' ,()=> { console .log('redis,ready' ) }) client.on("error" , (err ) => { console .log("Error " + err); }); const CONFIG = { key : 'nodejs:session' , maxAge : 86400000 , overwrite : true , httpOnly : false , signed : false , store : { async get (key, maxAge ) { let result = await getAsync(key); return JSON .parse(result); }, async set (key, sess, maxAge ) { let result = client.set(key, JSON .stringify(sess)); }, destroy (key ) { client.del(key); } } }; app.use(session(CONFIG, app));