koa学习笔记

准备

Koa是一种Web框架。
由Express原班人马打造的koa,致力于成为一个更小、更健壮、更富有表现力的Web框架。使用koa编写web应用,通过组合不同的generator,可以免除重复繁琐的回调函数嵌套,并极大地提升常用错误处理效率。Koa没有捆绑任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。

Koa is a new web framework designed by the team behind Express, which aims to be a smaller, more expressive, and more robust foundation for web applications and APIs. Through leveraging generators Koa allows you to ditch callbacks and greatly increase error-handling. Koa does not bundle any middleware within core, and provides an elegant suite of methods that make writing servers fast and enjoyable.

Koa需要node 7.6.0以上的版本,以支持ES6和async方法。

Hello World

下面是官网的例子

1
2
3
4
5
6
7
8
const Koa = require('koa')
const app = new Koa()

// 中间件
app.use(async (ctx, next) => {
ctx.body = 'Hello World'
})
app.listen(3000)

Koa应用程序

在执行new Koa()时创建的对象被称为Koa应用对象。
应用对象是带有node http服务的Koa接口。它可以处理中间件的注册,将http请求分发到中间件,进行默认错误处理,以及应对上下文,请求和响应对象进行配置。
上面的代码架设了一个http服务,监听3000端口。

中间件

Koa 是一个中间件框架,可以采用两种不同的方法来实现中间件:

  • async function
  • common function

上面的代码用的是async function(node v7.6+)。
中间件通常带有两个参数(ctx, next),ctx是一个请求的上下文(context),next是调用执行下游中间件的函数,在代码执行完成后通过then方法返回一个Promise

级联代码

Koa 中间件以一种非常传统的方式级联起来,如下面的例子,代码执行的顺序如注释。

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
const Koa = require('koa')
const app = new Koa()

// x-response-time
app.use(async (ctx, next) => {
// (1) 进入路由
const start = Date.now()
await next()
// (5) 再次进入 x-response-time 中间件,记录2次通过此中间件「穿越」的时间
const ms = Date.now() - start
ctx.set('X-Response-Time', `${ms}ms`)
// (6) 返回 ctx.body
})

// logger
app.use(async (ctx, next) => {
// (2) 进入 logger 中间件
const start = Date.now()
await next()
// (4) 再次进入 logger 中间件,记录2次通过此中间件「穿越」的时间
const ms = Date.now() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}`)
})

// response
app.use(async ctx => {
// (3) 进入 response 中间件,没有捕获到下一个符合条件的中间件,传递到 upstream
ctx.body = 'Hello World'
})

app.listen(3000)

一开始,用户的请求通过x-response-time和logger中间件。这两个中间件记录了一些请求细节。然后“穿过”response 中间件一次,最终结束请求,返回“Hello World”。
当程序运行到await next()时,代码流会暂停执行这个中间件到剩余代码,转而切换到下一个被定义的中间件执行代码,这样切换控制权的方式,被称为downstream,当没有下一个中间件执行downstream的时候,代码会逆序执行(回到await next()执行下一行)。

“级联”这个词许多人也许在CSS中听说过,如果你不能理解为什么在这里使用这个词,可以将这种路由结构想象成LESS的继承嵌套书写方式。
下面的“洋葱”图更能形象地表达。用户的请求先后经过登陆管理,状态码重定向,错误处理,cache缓存中间件,session会话中间件,routes路由中间件,应用。响应逆向经过这些步骤传递出去。
级联模型

Context(内容)

每个中间件都接受一个Koa的Context对象。Context将 node 的requestresponse对象封装到单个对象中。ctx通常用作上下文对象的参数名称。

ctx.request

Koa提供了一个Request对象作为Contextrequest属性,即ctx.request。 Koa的Request对象提供了用于处理 http请求的方法,该请求委托给 nodehttp模块的IncomingMessage
下面是一个检查请求客户端xml支持的示例。

1
2
3
4
5
6
app.use(async (ctx, next) => {
ctx.assert(ctx.request.accepts('xml'), 406);
// 相当于:
// if (!ctx.request.accepts('xml')) ctx.throw(406);
await next();
})

ctx.response

Koa提供了一个Response对象作为Contextresponse属性,即ctx.response。 Koa的Response对象提供了用于处理http响应的方法,该响应委托给ServerResponse
下面是一个使用Koa的`Response`对象将文件作为响应体流式传输的示例(返回一个页面)。

1
2
3
4
5
app.use(async (ctx, next) => {
await next()
ctx.response.type = 'html'
ctx.response.body = fs.createReadStream('index.html')
})

更详细的API参阅请求 API 参考, 响应 API 参考上下文 API 参考

koa-router

koa-router是koa的路由中间件。
可以先看一下原生路由和koa-router的基本用法的对比。

1
2
3
4
5
6
7
8
9
// 原生路由
app.use(async ctx => {
if (ctx.request.path !== '/') {
ctx.response.type = 'html';
ctx.response.body = '<a href="/">Index Page</a>';
} else {
ctx.response.body = 'Hello World';
}
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// koa-route
const router = require('koa-router');

router
.get('/', ctx => {
ctx.response.body = 'Hello World'
})
.get('/about', ctx => {
ctx.response.type = 'html'
ctx.response.body = '<a href="/">Index Page</a>'
})

app
.use(router.routes())
.use(router.allowedMethods())

router.get|put|post|patch|delete|del ⇒ Router

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
router
.get('/', function (ctx, next) {
ctx.body = 'Hello World!';
})
.post('/users', function (ctx, next) {
// ...
})
.put('/users/:id', function (ctx, next) {
// ...
})
.del('/users/:id', function (ctx, next) {
// ...
})
// router.all() can be used to match against all methods
.all('/users/:id', function (ctx, next) {
// ...
})

多个中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
router.get(
'/users/:id',
function (ctx, next) {
return User.findOne(ctx.params.id).then(function(user) {
ctx.user = user;
return next();
});
},
function (ctx) {
console.log(ctx.user);
// => { id: 17, name: "Alex" }
}
)

嵌套路由

1
2
3
4
5
6
7
8
9
10
11
12
13
const forums = new Router()
const posts = new Router()
posts
.get('/', function (ctx, next) {
// ...
})
.get('/:pid', function (ctx, next) {
// ...
})
forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods())

// responds to '/forums/123/posts' and '/forums/123/posts/123'
app.use(forums.routes())

路由前缀

1
2
3
4
5
6
const router = new Router({
prefix: '/users'
})

router.get('/', ...) // responds to '/users'
router.get('/:id', ...) // responds to '/users/:id'

URL参数

命名的路由参数会被捕获,并被添加到ctx.params

1
2
3
4
router.get('/:category/:title', (ctx, next) => {
console.log(ctx.params)
// => { category: 'programming', title: 'how-to-node' }
})

关于koa-router更多介绍参阅koa-router

参考