# 基本用法
# Context 对象
Context 对象,表示一次对话的上下文(包括 HTTP 请求和 HTTP 回复)
1 2 3 4 5 6 7 8 9
| const Koa = require('koa') const app = new Koa()
const main = ctx => { ctx.response.body = 'Hello World' }
app.use(main) app.listen(3000)
|
# HTTP Response 的类型
Koa 默认的返回类型是 text/plain
,如果想返回其他类型的内容,可以先用 ctx.request.accepts
判断一下,客户端希望接受什么数据(根据 HTTP Request 的 Accept
字段),然后使用 ctx.response.type
指定返回类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const Koa = require('koa') const app = new Koa()
const main = ctx => { if (ctx.request.accepts('xml')) { ctx.response.type = 'xml' ctx.response.body = '<data>Hello World</data>' } else if (ctx.request.accepts('json')) { ctx.response.type = 'json' ctx.response.body = { data: 'Hello World' } } else if (ctx.request.accepts('html')) { ctx.response.type = 'html' ctx.response.body = '<p>Hello World</p>' } else { ctx.response.type = 'text' ctx.response.body = 'Hello World' } }
app.use(main) app.listen(3000)
|
# 网页模板
实际开发中,返回给用户的网页往往都写成模板文件。可以让 Koa 先读取模板文件,然后将这个模板返回给用户
1 2 3 4 5 6
| const fs = require('fs')
const main = ctx => { ctx.response.type = 'html' ctx.response.body = fs.createReadStream('./demos/template.html') }
|
# 路由
# 原生路由
1 2 3 4 5 6 7 8
| const main = ctx => { if (ctx.request.path !== '/') { ctx.response.type = 'html' ctx.response.body = '<a href="/">Index Page</a>' } else { ctx.response.body = 'Hello World' } }
|
# koa-route 模块
1 2 3 4 5 6 7 8 9 10 11 12 13
| const route = require('koa-route')
const about = ctx => { ctx.response.type = 'html' ctx.response.body = '<a href="/">Index Page</a>' }
const main = ctx => { ctx.response.body = 'Hello World' }
app.use(route.get('/', main)) app.use(route.get('/about', about))
|
# 静态资源
1 2 3 4 5 6 7 8 9
| const path = require('path') const serve = require('koa-static')
const main = serve(path.join(__dirname)) app.use(main)
|
# 重定向
ctx.response.redirect()
方法可以发出一个 302 跳转,将用户导向另一个路由
1 2 3 4 5 6
| const redirect = ctx => { ctx.response.redirect('/') ctx.response.body = '<a href="/">Index Page</a>' }
app.use(route.get('/redirect', redirect))
|
# 中间件
# 中间件的概念
代码中的 logger
函数就叫做” 中间件”(middleware),因为它处在 HTTP Request 和 HTTP Response 中间,用来实现某种中间功能。 app.use()
用来加载中间件。基本上,Koa 所有的功能都是通过中间件实现的。每个中间件默认接受两个参数,第一个参数是 Context 对象,第二个参数是 next
函数。只要调用 next
函数,就可以把执行权转交给下一个中间件。
1 2 3 4 5
| const logger = (ctx, next) => { console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`) next() } app.use(logger)
|
# 中间件栈
多个中间件会形成一个栈结构(middle stack),以” 先进后出”(first-in-last-out)的顺序执行。
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
| const one = (ctx, next) => { console.log('>> one') next() console.log('<< one') }
const two = (ctx, next) => { console.log('>> two') next() console.log('<< two') }
const three = (ctx, next) => { console.log('>> three') next() console.log('<< three') }
app.use(one) app.use(two) app.use(three)
|
# 异步中间件
fs.readFile
是一个异步操作,必须写成 await fs.readFile()
,然后中间件必须写成 async 函数
1 2 3 4 5 6 7 8 9 10 11
| const fs = require('fs.promised') const Koa = require('koa') const app = new Koa()
const main = async function(ctx, next) { ctx.response.type = 'html' ctx.response.body = await fs.readFile('./demos/template.html', 'utf8') }
app.use(main) app.listen(3000)
|
# 中间件的合成
1 2 3 4 5 6 7 8 9 10 11 12 13
| const compose = require('koa-compose')
const logger = (ctx, next) => { console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`) next() }
const main = ctx => { ctx.response.body = 'Hello World' }
const middlewares = compose([logger, main]) app.use(middlewares)
|
# 错误处理
# 500 错误
1 2 3
| const main = ctx => { ctx.throw(500) }
|
# 404 错误
1 2 3 4
| const main = ctx => { ctx.response.status = 404 ctx.response.body = 'Page Not Found' }
|
# 处理错误的中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const handler = async (ctx, next) => { try { await next() } catch (err) { ctx.response.status = err.statusCode || err.status || 500 ctx.response.body = { message: err.message } } } const main = ctx => { ctx.throw(500) }
app.use(handler) app.use(main)
|
# error 事件的监听
运行过程中一旦出错,Koa 会触发一个 error
事件。监听这个事件,也可以处理错误
1 2 3 4 5 6 7
| const main = ctx => { ctx.throw(500); };
app.on('error', (err, ctx) => console.error('server error', err); );
|
# 释放 error 事件
如果错误被 try...catch
捕获,就不会触发 error
事件。这时,必须调用 ctx.app.emit()
,手动释放 error
事件,才能让监听函数生效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const handler = async (ctx, next) => { try { await next() } catch (err) { ctx.response.status = err.statusCode || err.status || 500 ctx.response.type = 'html' ctx.response.body = '<p>Something wrong, please contact administrator.</p>' ctx.app.emit('error', err, ctx) } }
const main = ctx => { ctx.throw(500) }
app.on('error', function(err) { console.log('logging error ', err.message) console.log(err) })
|
# Web App 的功能
# cookies
1 2 3 4 5 6
| const main = function(ctx) { const n = Number(ctx.cookies.get('view') || 0) + 1 ctx.cookies.set('view', n) ctx.response.body = n + ' views' }
|
# 表单
1 2 3 4 5 6 7 8 9 10 11 12
| const koaBody = require('koa-body')
const main = async function(ctx) { const body = ctx.request.body if (!body.name) ctx.throw(400, '.name required') ctx.body = { name: body.name } }
app.use(koaBody())
|
# 文件上传
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 os = require('os') const path = require('path') const koaBody = require('koa-body')
const main = async function(ctx) { const tmpdir = os.tmpdir() const filePaths = [] const files = ctx.request.body.files || {}
for (let key in files) { const file = files[key] const filePath = path.join(tmpdir, file.name) const reader = fs.createReadStream(file.path) const writer = fs.createWriteStream(filePath) reader.pipe(writer) filePaths.push(filePath) }
ctx.body = filePaths }
app.use(koaBody({ multipart: true }))
|