## 入门

### Hello World

Koa 需要 node v7.6.0 或更高版本来支持ES2015、异步方法，你可以安装自己支持的 `node` 版本

- 安装依赖

      $ mkdir myapp # 创建目录
      $ cd myapp    # 进入目录
      $ nvm install 7
      $ npm init -y # 初始化一个配置
      $ npm install koa # 安装依赖

- 入口文件 `index.js` 添加代码：

      const Koa = require('koa');
      const app = new Koa();

      app.use(async ctx => {
        ctx.body = 'Hello World';
      });

      app.listen(3000);

- 使用以下命令运行应用程序

      $ node index.js

### 级联

    const Koa = require('koa');
    const app = new Koa();
    // X-Response-Time x 响应时间
    app.use(async (ctx, next) => {
      const start = Date.now();
      await next();
      const ms = Date.now() - start;
      ctx.set('X-Response-Time', `${ms}ms`);
    });
    // 记录器 logger
    app.use(async (ctx, next) => {
      const start = Date.now();
      await next();
      const ms = Date.now() - start;
      console.log(
        `${ctx.method} ${ctx.url} - ${ms}`
      );
    });
    // 响应 response
    app.use(async ctx => {
      ctx.body = 'Hello World';
    });
    app.listen(3000);

### 配置

:- | :-
--- | ---
`app.env` | 默认为 `NODE_ENV` 或 `development`
`app.keys` | 签名 `cookie` 密钥数组
`app.proxy` | 何时信任真正的代理头字段
`app.subdomainOffset` | 要忽略的 `.subdomains` 的偏移量，默认为 `2`
`app.proxyIpHeader` | 代理 `ip` 头，默认为 `X-Forwarded-For`
`app.maxIpsCount` | 从代理 `ip` 头读取的最大 `ips` 数，默认为 `0`（表示无穷大）

### app.callback()

:- | :-
--- | ---
`app.listen(...)` # | 为一个绑定 `3000` 端口的简单 `Koa` 应用
`app.callback()` # | 返回一个适合 `http.createServer()` 方法的回调函数用来处理请求
`app.use(function)` # | 添加指定的中间件，详情请看 Middleware
`app.keys` # | 设置签名 `cookie` 密钥
`app.context` # | 从中创建 `ctx` 的原型

### 错误处理

    app.on('error', (err, ctx) => {
      log.error('server error', err, ctx)
    });

默认情况下 `Koa` 会将所有错误信息输出到 `stderr`， 除非 `app.silent` 是 `true`。当 `err.status` 是 `404` 或者 `err.expose` 时，默认错误处理程序也不会输出错误

### Context 示例

    app.use(async ctx => {
      ctx; // 这是上下文 Context
      ctx.request;  // 这是 koa Request
      ctx.response; // 这是 koa Response
    });

### app.listen(...)

    const Koa = require('koa');
    const app = new Koa();
    app.listen(3000);

`app.listen(...)` 实际上是以下代码的语法糖:

    const http = require('http');
    const Koa = require('koa');
    const app = new Koa();
    http.createServer(app.callback()).listen(3000);

这意味着您可以同时支持 `HTTPS` 和 `HTTPS`，或者在 `多个端口` 监听同一个应用

    const http = require('http');
    const https = require('https');
    const Koa = require('koa');
    const app = new Koa();
    http.createServer(app.callback()).listen(3000);
    https.createServer(app.callback()).listen(3001);

### ctx.throw 示例

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

`this.throw('name required', 400)` 等价于

    const err = new Error('name required');
    err.status = 400;
    err.expose = true;
    throw err;

### ctx.assert 示例

    ctx.assert(
      ctx.state.user,
      401,
      'User not found. Please login!'
    );

### Context(上下文) API

:- | :-
--- | ---
`ctx.req` | Node 的 request 对象
`ctx.res` | Node 的 response 对象
`ctx.request` | Koa 的 Request 对象
`ctx.response` | Koa 的 Response 对象
`ctx.state` | 推荐的命名空间，用于通过中间件传递信息到前端视图
`ctx.app` | 应用实例引用
`ctx.app.emit` | 发出由第一个参数定义的类型的事件
`ctx.cookies.get(name, [options])` | 获得 `cookie` 中名为 `name` 的值
`ctx.cookies.set(name, value, [options])` | 设置 `cookie` 中名为 `name` 的值
`ctx.throw([status], [msg], [properties])` | 抛出包含 `.status` 属性的错误，默认为 `500`
`ctx.assert(value, [status], [msg], [properties])` | 当 `!value` 时， `Helper` 方法抛出一个类似 `.throw()` 的错误
`ctx.respond` | 避免使用 `Koa` 的内置响应处理功能，您可以直接赋值 `this.repond = false`

### ctx.cookies.set 参数

:- | :-
--- | ---
`maxAge` | 表示从Date开始的毫秒数 `now()` 到期。
`expires` | 一个 `Date` 对象，指示 `cookie` 的到期日期（默认情况下在会话结束时到期）
`path` | 表示 `cookie` 路径的字符串（默认为`/`）
`domain` | 表示 `cookie` 的域的字符串（无默认值）
`secure` | 一个布尔值，指示 `cookie` 是否只通过HTTPS发送（HTTP默认为false，HTTPS默认为true）。阅读有关此选项的更多信息
`httpOnly` | 一个布尔值，指示cookie是否只通过HTTP（S）发送，而不可用于客户端 JavaScript（默认为true）
`sameSite` | 一个布尔值或字符串，指示cookie是否为“同一站点”cookie（默认为false）。这可以设置为“strict”、“lax”、“none”或true（映射为“strect”）
`signed` | 一个布尔值，指示是否对cookie进行签名（默认为false）。如果这是真的，还将发送另一个附加了.sig后缀的同名cookie，其中一个27字节的url安全base64 SHA1值表示cookie name=cookie值相对于第一个Keygrip键的哈希值。此签名密钥用于在下次收到cookie时检测篡改
`overwrite` | 一个布尔值，指示是否覆盖以前设置的同名 `cookie`（默认为false）。如果为true，则在设置此Cookie时，将从set-Cookie标头中筛选出在同一请求期间设置的具有相同名称的所有Cookie（无论路径或域如何）

### 请求(Request)

:- | :-
--- | ---
`request.header` | 请求头对象
`request.header=` | 设置请求头对象
`request.headers` | 请求头对象。等价于 request.header.
`request.headers=` | 设置请求头对象。 等价于request.header=.
`request.method` | 请求方法
`request.method=` | 设置请求方法, 在实现中间件时非常有用，比如 methodOverride()
`request.length` | 以数字的形式返回 request 的内容长度(Content-Length)，或者返回 undefined。
`request.url` | 获得请求url地址.
`request.url=` | 设置请求地址，用于重写url地址时
`request.originalUrl` | 获取请求原始地址
`request.origin` | 获取URL原始地址, 包含 protocol 和 host
`request.href` | 获取完整的请求URL, 包含 protocol, host 和 url
`request.path` | 获取请求路径名
`request.path=` | 设置请求路径名并保留当前查询字符串
`request.querystring` | 获取查询参数字符串(url中?后面的部分)，不包含?
`request.querystring=` | 设置原始查询字符串
`request.search` | 获取查询参数字符串，包含?
`request.search=` | 设置原始查询字符串
`request.host` | 获取 host (hostname:port)。 当 app.proxy 设置为 true 时，支持 X-Forwarded-Host
`request.hostname` | 获取 hostname。当 app.proxy 设置为 true 时，支持 X-Forwarded-Host。
`request.URL` | 获取 WHATWG 解析的对象.
`request.type` | 获取请求 Content-Type，不包含像 "charset" 这样的参数。
`request.charset` | 获取请求 charset，没有则返回 `undefined`
`request.query` | 将查询参数字符串进行解析并以对象的形式返回，如果没有查询参数字字符串则返回一个空对象
`request.query=` | 根据给定的对象设置查询参数字符串
`request.fresh` | 检查请求缓存是否 "fresh"(内容没有发生变化)
`request.stale` | 与 req.fresh 相反
`request.protocol` | 返回请求协议，"https" 或者 "http"
`request.secure` | 简化版 this.protocol == "https"，用来检查请求是否通过 TLS 发送
`request.ip` | 请求远程地址，当 app.proxy 设置为 true 时，支持 X-Forwarded-Host
`request.ips` | 当 X-Forwarded-For 存在并且 app.proxy 有效，将会返回一个有序（从 upstream 到 downstream）ip 数组
`request.subdomains` | 以数组形式返回子域名
`request.is(types...)` | 检查请求所包含的 "Content-Type" 是否为给定的 type 值
`request.accepts(types)` | 检查给定的类型 types(s) 是否可被接受，当为 true 时返回最佳匹配，否则返回 false
`request.acceptsEncodings(encodings)` | 检查 `encodings` 是否可以被接受，当为 `true` 时返回最佳匹配，否则返回 `false`
`request.acceptsCharsets(charsets)` | 检查 `charsets` 是否可以被接受，如果为 `true` 则返回最佳匹配，否则返回 `false`
`request.acceptsLanguages(langs)` | 检查 `langs` 是否可以被接受，如果为 `true` 则返回最佳匹配，否则返回 `false`
`request.idempotent` | 检查请求是否为幂等(idempotent)
`request.socket` | 返回请求的socket
`request.get(field)` | 返回请求头

### 响应(Response)

:- | :-
--- | ---
`response.header` | Response header 对象
`response.headers` | Response header 对象。等价于 response.header.
`response.socket` | Request socket.
`response.status` | 获取响应状态。 默认情况下，response.status设置为404，而不像node's res.statusCode默认为200。
`response.status=` | 通过数字设置响应状态
`response.message` | 获取响应状态消息。默认情况下, response.message关联response.status。
`response.message=` | 将响应状态消息设置为给定值。
`response.length=` | 将响应Content-Length设置为给定值。
`response.length` | 如果 Content-Length 作为数值存在，或者可以通过 ctx.body 来进行计算，则返回相应数值，否则返回 undefined。
`response.body` | 获取响应体。
`response.body=` | 设置响应体为如 `string`,`Buffer`,`Stream`,`Object|Array`,`null`
`response.get(field)` | 获取 response header 中字段值，field 不区分大小写
`response.set(field, value)` | 设置 response header 字段 field 的值为 value
`response.append(field, value)` | 添加额外的字段field 的值为 val
`response.set(fields)` | 使用对象同时设置 response header 中多个字段的值
`response.remove(field)` | 移除 response header 中字段 filed
`response.type` | 获取 response Content-Type，不包含像"charset"这样的参数
`response.type=` | 通过 mime 类型的字符串或者文件扩展名设置 response Content-Type
`response.is(types...)` | 跟 `ctx.request.is()` 非常类似。用来检查响应类型是否是所提供的类型之一
`response.redirect(url, [alt])` | 执行 [302] 重定向到对应 url
`response.attachment([filename])` | 设置 "attachment" 的 Content-Disposition，用于给客户端发送信号来提示下载
`response.headerSent` | 检查 response header 是否已经发送，用于在发生错误时检查客户端是否被通知。
`response.lastModified` | 如果存在 Last-Modified，则以 Date 的形式返回。
`response.lastModified=` | 以 UTC 格式设置 Last-Modified。您可以使用 Date 或 date 字符串来进行设置。
`response.etag=` | 设置 包含 "s 的 ETag
`response.vary(field)` | 不同于field.
`response.flushHeaders()` | 刷新任何设置的响应头，并开始响应体

### 请求(Request)别名

以下访问器和别名与 Request 等价：

- `ctx.header`
- `ctx.headers`
- `ctx.method`
- `ctx.method=`
- `ctx.url`
- `ctx.url=`
- `ctx.originalUrl`
- `ctx.origin`
- `ctx.href`
- `ctx.path`
- `ctx.path=`
- `ctx.query`
- `ctx.query=`
- `ctx.querystring`
- `ctx.querystring=`
- `ctx.host`
- `ctx.hostname`
- `ctx.fresh`
- `ctx.stale`
- `ctx.socket`
- `ctx.protocol`
- `ctx.secure`
- `ctx.ip`
- `ctx.ips`
- `ctx.subdomains`
- `ctx.is()`
- `ctx.accepts()`
- `ctx.acceptsEncodings()`
- `ctx.acceptsCharsets()`
- `ctx.acceptsLanguages()`
- `ctx.get()`

### 响应(Response)别名

以下访问器和别名与 Response 等价：

- `ctx.body`
- `ctx.body=`
- `ctx.status`
- `ctx.status=`
- `ctx.message`
- `ctx.message=`
- `ctx.length=`
- `ctx.length`
- `ctx.type=`
- `ctx.type`
- `ctx.headerSent`
- `ctx.redirect()`
- `ctx.attachment()`
- `ctx.set()`
- `ctx.append()`
- `ctx.remove()`
- `ctx.lastModified=`
- `ctx.etag=`

### request.fresh 示例

    // freshness 检查需要状态 20x 或 304
    ctx.status = 200;
    ctx.set('ETag', '123');

    // 缓存正常
    if (ctx.fresh) {
      ctx.status = 304;
      return;
    }

    // 缓存已过时
    // 获取新数据
    ctx.body = await db.find('something');

### ctx.is 示例

    // Content-Type: text/html; charset=utf-8
    ctx.is('html'); // => 'html'
    ctx.is('text/html'); // => 'text/html'
    ctx.is('text/*', 'text/html');
    // => 'text/html'
    // 当 Content-Type 为 application/json 时
    ctx.is('json', 'urlencoded'); // => 'json'
    ctx.is('application/json');
    // => 'application/json'
    ctx.is('html', 'application/*');
    // => 'application/json'

    ctx.is('html'); // => false

### ctx.accepts 示例

    // 接受: text/*, application/json
    ctx.accepts('html');
    // => "html"
    ctx.accepts('text/html');
    // => "text/html"
    ctx.accepts('json', 'text');
    // => "json"
    ctx.accepts('application/json');
    // => "application/json"

### request.acceptsCharsets 示例

    // Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5
    ctx.acceptsCharsets('utf-8', 'utf-7');
    // => "utf-8"

    ctx.acceptsCharsets(['utf-7', 'utf-8']);
    // => "utf-8"

检查 `charsets` 是否可以被接受，如果为 `true` 则返回最佳匹配， 否则返回 `false`

### response.set 示例

    ctx.set({
      'Etag': '1234',
      'Last-Modified': date
    });

使用对象同时设置 response header 中多个字段的值

### response.type 示例

    const ct = ctx.type;
    // => "image/png"

获取 response Content-Type，不包含像"charset"这样的参数