Koa2学习笔记

224 阅读3分钟
原文链接: zhuanlan.zhihu.com

此篇文章纯属个人笔记。

Koa2框架更加轻量, 优雅, 支持async/await + Promise来处理异步操作, 十分方便作为底层框架进行定制开发。

  • 实现Koa2的hello world版本
const Koa = require('koa')
const app = new Koa()
const port = 3000

app.use(async (ctx, next) => {
    await next()
    ctx.response.type = 'text/html'
    ctx.response.body = 'hello world'
})

app.listen(port, () => {
    console.log(`server is running on the port: ${port}`)
})


  • Context对象

上下文(Context)对象: Koa把node的request对象, response对象封装在Context对象, 所以你也可以把Context对象称为一次对话的上下文, 我们通过加工Context对象就可以控制返回给用户的内容。

下面这段Koa源码可以看见创建Context对象做了什么:


/**
   * Initialize a new context.
   *
   * @api private
   */

  createContext(req, res) {
    const context = Object.create(this.context);
    const request = context.request = Object.create(this.request);
    const response = context.response = Object.create(this.response);
    context.app = request.app = response.app = this;
    // node的request对象
    context.req = request.req = response.req = req;
    // node的response对象
    context.res = request.res = response.res = res;
    request.ctx = response.ctx = context;
    // Koa的response对象
    request.response = response;
    // Koa的request对象
    response.request = request;
    // 请求原始URL。
    context.originalUrl = request.originalUrl = req.url;
    // 命名空间
    context.state = {};
    return context;
  }


Koa中每个请求都会创建一个Context对象。

callback() {
    const fn = compose(this.middleware);

    if (!this.listenerCount('error')) this.on('error', this.onerror);

    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };

    return handleRequest;
  }



Context对象内置了一些常用属性,比如ctx.req, ctx,res, ctx.request, ctx.response等, 我们也可以在Context对象上自定义一些属性,配置等以供全局使用。


下面我们可以打印一下ctx内容。

app.use(async (ctx, next) => {
    console.log(ctx)
    await next()
    ctx.response.type = 'text/html'
    ctx.response.body = 'hello world'
})


{ 
  request:
   { method: 'GET',
     url: '/',
     header:
      { host: '192.168.2.103:3000',
        'user-agent':
         'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
        accept:
         'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
        'accept-encoding': 'gzip, deflate',
        'accept-language': 'zh-CN,zh;q=0.9',
        'cache-control': 'max-age=0',
        'proxy-connection': 'keep-alive',
        'upgrade-insecure-requests': '1',
        'x-lantern-version': '5.2.2' } },
  response: { status: 404, message: 'Not Found', header: {} },
  app: { subdomainOffset: 2, proxy: false, env: 'development' },
  originalUrl: '/',
  req: '<original node req>',
  res: '<original node res>',
  socket: '<original node socket>'
}

写一个小demo感受下ctx.request对象的使用。

// 请求url => /search?keywords=content&pageSize=10&pageIndex=1

app.use(async (ctx, next) => {
    await next()
    ctx.response.type = 'text/html'
    ctx.response.body = {
        url: ctx.request.url, // 获取请求url
        query: ctx.request.query, // 获取解析的查询字符串
        querystring: ctx.request.querystring, // 获取原始的的查询字符串
        search: ctx.request.search // 获取带?的查询字符串
    }
})

{
    "url":"/search?keywords=content&pageSize=10&pageIndex=1",
    "query":{
        "keywords":"content",
        "pageSize":"10",
        "pageIndex":"1"
    },
    "querystring":"keywords=content&pageSize=10&pageIndex=1",
    "search":"?keywords=content&pageSize=10&pageIndex=1"
}

上述的例子你可以用来处理大多数情况下的GET请求, 现在我们再学习一个demo来处理POST请求。

想要通过Koa来获取POST请求的参数上可以借助node原生的req对象。

app.use(async (ctx, next) => {
    let postData = ''
    await next()
    ctx.response.type = 'text/html'
    ctx.req.on('data', (data) => {
        postData += data
    })
    ctx.req.on('end', () => {
        console.log('over', postData)
    })
})

通过终端发送一个POST请求或者Postman也可以。

curl -d "name=kobe&age=10" http://ip:3000/

// 结果可以获取到
name=kobe&age=10

当然开发中我们使用一些中间件来处理POST请求如koa-bodyparser。


讲完了Koa的request对象, 接下来学习下Koa的response对象。

ctx.response.body = '' // 设置响应的主体
ctx.response.status = '' // 设置请求的响应状态, 如200, 204, 500
ctx.response.type = '' // 设置响应的Content-Type, 如 html, image/png, text/plain.


app.use(async (ctx, next) => {
    await next()
    ctx.response.status = 200
    // request.accepts(types)
    // 内容协商:判断客户端能接受的数据类型
    if (ctx.request.accepts('json')) {
        ctx.response.type = 'json'
        ctx.response.body = {
            "name": "kobe"
        }
    } else if (ctx.request.accepts('html')) {
        ctx.response.type = 'html'
        ctx.response.body = '<h3>h3</h3>'
    } else {
        ctx.response.type = 'text'
        ctx.response.body = 'hello world'
    }
})


ctx.state是推荐的命名空间, 用于通过中间件来传递消息和前端视图。

// 把user属性存在state对象中,用以另外一个中间件读取
ctx.state.user = yield User.find(id)


ctx.cookies用于设置和获取cookie。

ctx.cookies.get(name, [options]) // 获取cookie
ctx.cookies.set(name, value, [options])  // 设置cookie


ctx.throw用于抛出错误,把错误返回给用户。

ctx.throw([status], [msg], [properties]) // .status 属性默认为 500 的错误

ctx.throw(400);
ctx.throw(400, 'name required');
ctx.throw(400, 'name required', { user: user });


note: 未完明天继续更新。