node.js的下一代Web框架

Introduction

Koa是由Express背后的团队设计的一种新的Web框架,旨在为Web应用程序和API提供更小,更富表现力和更强大的基础. 通过利用异步功能,Koa允许您放弃回调并大大提高错误处理能力. Koa在其核心内未捆绑任何中间件,并且提供了一套优雅的方法来使编写服务器变得快速且愉悦.

Installation

Koa要求节点v7.6.0或更高版本才能支持ES2015和异步功能.

您可以使用喜欢的版本管理器快速安装受支持的节点版本:

$ nvm install 7
$ npm i koa
$ node my-koa-app.js

Async Functions with Babel

要在节点<7.6版本中的Koa中使用async功能,我们建议使用babel的require hook .

require('babel-register');
// require the rest of the app that needs to be transpiled after the hook
const app = require('./app');

要解析和传递异步函数,您至少应具有transform-async-generatortransform-async-to-module-method插件. 例如,在您的.babelrc文件中,您应该具有:

{
  "plugins": ["transform-async-to-generator"]
}

您还可以使用带有目标选项"node": "current" 预设 .

Application

Koa应用程序是一个对象,其中包含一系列中间件功能,这些中间件功能可应要求以类似栈的方式组成和执行. Koa与您可能遇到的许多其他中间件系统类似,例如Ruby的Rack,Connect等-但是做出了一项关键设计决策,决定在其他较低层的中间件层提供高层"糖". 这提高了互操作性,鲁棒性,并使编写中间件更加有趣.

这包括用于常见任务的方法,例如内容协商,缓存新鲜度,代理支持以及重定向. 尽管提供了大量有用的方法,但由于没有捆绑中间件,所以Koa占用的资源很少.

强制性的hello world应用程序:

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

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

app.listen(3000);

Cascading

Koa中间件以一种您可能已经习惯使用类似工具的更传统的方式进行级联-以前这很难使用户易于使用节点的回调. 但是,使用异步功能,我们可以实现"真正的"中间件. 与Connect的实现相反,该实现仅通过一系列功能传递控制权直到一个返回,Koa调用"下游",然后控制流向"上游".

The following example responds with "Hello World", however first the request flows through the x-response-time and logging middleware to mark when the request started, then continue to yield control through the response middleware. When a middleware invokes next() the function suspends and passes control to the next middleware defined. After there are no more middleware to execute downstream, the stack will unwind and each middleware is resumed to perform its upstream behaviour.

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

// logger

app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.get('X-Response-Time');
  console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});

// x-response-time

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set('X-Response-Time', `${ms}ms`);
});

// response

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

app.listen(3000);

Settings

应用程序设置是app实例上的属性,目前支持以下属性:

app.listen(...)

Koa应用程序不是HTTP服务器的一对一表示. 可以将一个或多个Koa应用程序安装在一起,以使用单个HTTP服务器形成更大的应用程序.

创建并返回一个HTTP服务器,将给定参数传递给Server#listen() . 这些参数记录在nodejs.org上 . 以下是绑定到端口3000的无用的Koa应用程序:

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);

这意味着您可以启动与HTTP和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);

app.callback()

返回适合于http.createServer()方法的回调函数来处理请求. 您也可以使用此回调函数将Koa应用程序安装在Connect / Express应用程序中.

app.use(function)

将给定的中间件功能添加到此应用程序. 有关更多信息,请参见中间件 .

app.keys=

设置签名的Cookie密钥.

这些将传递给KeyGrip ,但是您也可以传递自己的KeyGrip实例. 例如,以下是可接受的:

app.keys = ['im a newer secret', 'i like turtle'];
app.keys = new KeyGrip(['im a newer secret', 'i like turtle'], 'sha256');

这些键可以旋转,并在使用{ signed: true }选项对cookie进行签名时使用:

ctx.cookies.set('name', 'tobi', { signed: true });

app.context

app.context is the prototype from which ctx is created. You may add additional properties to ctx by editing app.context. This is useful for adding properties or methods to ctx to be used across your entire app, which may be more performant (no middleware) and/or easier (fewer require()s) at the expense of relying more on ctx, which could be considered an anti-pattern.

例如,要从ctx添加对数据库的引用:

app.context.db = db();

app.use(async ctx => {
  console.log(ctx.db);
});

Note:

Error Handling

默认情况下,除非app.silenttrue否则将所有错误输出到stderr. 当err.status404err.exposetrue时,默认错误处理程序也不会输出错误. 要执行自定义的错误处理逻辑(例如集中式日志记录),您可以添加"错误"事件监听器:

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

如果错误是在REQ / RES周期,这是不可能的,以响应客户端的Context实例也通过了:

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

当发生错误并且仍然有可能响应客户端(即没有数据写入套接字)时,Koa会以500"内部服务器错误"进行适当响应. 在任何一种情况下,都会出于记录目的发出应用程序级别的"错误".

Context

Koa上下文将节点的requestresponse对象封装到一个对象中,该对象提供了许多用于编写Web应用程序和API的有用方法. 这些操作在HTTP服务器开发中经常使用,以至于在此级别而不是更高级别的框架中添加它们,这将迫使中间件重新实现此通用功能.

每个请求都会创建一个Context ,并在中间件中将其作为接收方或ctx标识符进行引用,如以下代码段所示:

app.use(async ctx => {
  ctx; // is the Context
  ctx.request; // is a Koa Request
  ctx.response; // is a Koa Response
});

为了方便起见,许多上下文的访问器和方法只是委托给它们的ctx.requestctx.response等效项,否则它们是相同的. 例如, ctx.typectx.length委托给response对象,而ctx.pathctx.method委托给request .

API

特定于Context方法和访问器.

ctx.req

Node's request object.

ctx.res

Node's response object.

不支持绕过Koa的响应处理. 避免使用以下节点属性:

ctx.request

Koa Request对象.

ctx.response

Koa Response对象.

ctx.state

推荐的名称空间,用于通过中间件和前端视图传递信息.

ctx.state.user = await User.find(id);

ctx.app

应用程序实例参考.

ctx.app.emit

Koa应用程序扩展了内部EventEmitter . ctx.app.emit发出一个事件,该事件的类型由第一个参数定义. 对于每个事件,您都可以连接"侦听器",该函数在发出事件时会被调用. 有关更多信息,请参考错误处理文档 .

ctx.cookies.get(name, [options])

使用options获取Cookie name

Koa使用cookie模块,在其中简单地传递选项.

ctx.cookies.set(name, value, [options])

使用options将cookie name设置为value

Koa使用cookie模块,在其中简单地传递选项.

ctx.throw([status], [msg], [properties])

.status属性默认为500引发错误的Helper方法,这将使Koa能够正确响应. 允许以下组合:

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

例如ctx.throw(400, 'name required')等效于:

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

请注意,这些是用户级别的错误,并带有err.expose标记, err.expose意味着该消息适用于客户端响应,对于错误消息,通常情况并非如此,因为您不想泄漏故障详细信息.

您可以选择传递一个按原样合并到错误中的properties对象,该properties对象用于修饰机器友好的错误,这些错误会报告给上游的请求者.

ctx.throw(401, 'access_denied', { user: user });

Koa使用http-errors来创建错误. status只能作为第一个参数传递.

ctx.assert(value, [status], [msg], [properties])

!value时,抛出类似于.throw()的错误的Helper方法. 类似于节点的assert()方法.

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

Koa使用http-assert进行断言.

ctx.respond

要绕过Koa的内置响应处理,您可以显式设置ctx.respond = false; . 如果您要写入原始res对象,而不是让Koa为您处理响应,请使用此方法.

请注意,Koa 支持使用此功能. 这可能会破坏Koa中间件和Koa本身的预期功能. 使用此属性被视为黑客,并且对于那些希望在Koa中使用传统的fn(req, res)函数和中间件的人来说只是一种便利.

Request aliases

以下访问器和别名Request等效项:

Response aliases

以下访问器和别名响应等效项:

Request

Koa Request对象是节点普通请求对象之上的抽象,提供了对每天HTTP服务器开发有用的附加功能.

API

request.header

请求标头对象.

request.header=

Set request header object.

request.headers

请求标头对象. 别名为request.header .

request.headers=

设置请求标头对象. 别名为request.header= .

request.method

请求方法.

request.method=

设置请求方法,对于实现诸如methodOverride()中间件很有用.

request.length

返回请求Content-Length作为数字(如果存在)或undefined .

request.url

获取请求URL.

request.url=

设置请求URL,对于URL重写很有用.

request.originalUrl

获取请求原始URL.

request.origin

获取URL的来源,包括protocolhost .

ctx.request.origin
// => http://example.com

request.href

获取完整的请求URL,包括protocolhosturl .

ctx.request.href;
// => http://example.com/foo/bar?q=1

request.path

获取请求路径名.

request.path=

设置请求路径名并保留查询字符串(如果存在).

request.querystring

获取无效的原始查询字符串? .

request.querystring=

设置原始查询字符串.

获取带有?原始查询字符串 .

request.search=

设置原始查询字符串.

request.host

获取主机(主机名:端口)(如果存在). 当app.proxytrue时支持X-Forwarded-Host ,否则使用Host .

request.hostname

获取主机名(如果存在). 当app.proxytrue时支持X-Forwarded-Host ,否则使用Host .

如果主机是IPv6,则Koa会将解析委托给WHATWG URL API请注意,这可能会影响性能.

request.URL

获取WHATWG解析的URL对象.

request.type

获取请求Content-Type无效的参数,例如"字符集".

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

request.charset

获取请求字符集(如果存在)或undefined

ctx.request.charset;
// => "utf-8"

request.query

获取已解析的查询字符串,如果不存在查询字符串,则返回一个空对象. 请注意,这吸气支持嵌套解析.

例如" color = blue&size = small":

{
  color: 'blue',
  size: 'small'
}

request.query=

将查询字符串设置为给定的对象. 请注意,此setter 支持嵌套的对象.

ctx.query = { next: '/login' };

request.fresh

检查请求缓存是否为"新鲜",即内容没有更改. 此方法用于If-None-Match / ETagIf-Modified-SinceLast-Modified之间的缓存协商. 在设置一个或多个这些响应头之后,应该引用它.

// freshness check requires status 20x or 304
ctx.status = 200;
ctx.set('ETag', '123');

// cache is ok
if (ctx.fresh) {
  ctx.status = 304;
  return;
}

// cache is stale
// fetch new data
ctx.body = await db.find('something');

request.stale

相反的request.fresh .

request.protocol

返回请求协议," https"或" http". 当app.proxytrue时,支持X-Forwarded-Proto .

request.secure

ctx.protocol == "https"用于检查是否通过TLS发出了请求.

request.ip

请求远程地址. 当app.proxytrue时,支持X-Forwarded-For .

request.ips

当存在X-Forwarded-For并且启用app.proxy ,将返回这些app.proxy的数组,从上游->下游开始排序. 禁用后,将返回一个空数组.

request.subdomains

以数组形式返回子域.

子域是主机在应用程序主域之前的点分隔部分. 默认情况下,应用程序的域假定为主机的最后两个部分. 可以通过设置app.subdomainOffset来更改.

例如,如果所访问的"tobi.ferrets.example.com":如果app.subdomainOffset没有被设置, ctx.subdomains["ferrets", "tobi"] 如果app.subdomainOffset为3,则ctx.subdomains["tobi"] .

request.is(types...)

检查传入的请求是否包含" Content-Type"标头字段,并且其中包含任何给定的mime type s. 如果没有请求正文,则返回null . 如果没有内容类型,或者匹配失败,则返回false . 否则,它将返回匹配的内容类型.

// With Content-Type: text/html; charset=utf-8
ctx.is('html'); // => 'html'
ctx.is('text/html'); // => 'text/html'
ctx.is('text/*', 'text/html'); // => 'text/html'

// When Content-Type is application/json
ctx.is('json', 'urlencoded'); // => 'json'
ctx.is('application/json'); // => 'application/json'
ctx.is('html', 'application/*'); // => 'application/json'

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

例如,如果要确保仅将图像发送到给定的路线:

if (ctx.is('image/*')) {
  // process
} else {
  ctx.throw(415, 'images only!');
}

Content Negotiation

Koa的request对象包括由acceptsnegotiator支持的有用的内容协商实用程序. 这些实用程序是:

如果未提供任何类型,则返回所有可接受的类型.

如果提供了多种类型,则将返回最佳匹配. 如果未找到匹配项,则返回false ,您应该向客户端发送406 "Not Acceptable"响应.

如果缺少可接受的标头(任何类型都可接受),则将返回第一种类型. 因此,您提供的类型的顺序很重要.

request.accepts(types)

检查给定type(s)是否可接受,为true时返回最佳匹配,否则为false . type值可以是一个或多个mime类型字符串,例如" application / json",扩展名,例如" json"或数组["json", "html", "text/plain"] .

// Accept: text/html
ctx.accepts('html');
// => "html"

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

// Accept: text/*, application/json
ctx.accepts('image/png');
ctx.accepts('png');
// => false

// Accept: text/*;q=.5, application/json
ctx.accepts(['html', 'json']);
ctx.accepts('html', 'json');
// => "json"

// No Accept header
ctx.accepts('html', 'json');
// => "html"
ctx.accepts('json', 'html');
// => "json"

您可以根据需要多次调用ctx.accepts() ,或使用开关:

switch (ctx.accepts('json', 'html', 'text')) {
  case 'json': break;
  case 'html': break;
  case 'text': break;
  default: ctx.throw(406, 'json, html, or text only');
}

request.acceptsEncodings(encodings)

检查encodings是否可接受,如果为true,则返回最佳匹配,否则为false . 请注意,您应该将identity作为编码之一!

// Accept-Encoding: gzip
ctx.acceptsEncodings('gzip', 'deflate', 'identity');
// => "gzip"

ctx.acceptsEncodings(['gzip', 'deflate', 'identity']);
// => "gzip"

如果未提供任何参数,则将所有接受的编码作为数组返回:

// Accept-Encoding: gzip, deflate
ctx.acceptsEncodings();
// => ["gzip", "deflate", "identity"]

请注意,如果客户端显式发送identity;q=0 ,则identity编码(这意味着不进行编码)可能是不可接受的. 尽管这是一种极端情况,但您仍应处理此方法返回false .

request.acceptsCharsets(charsets)

检查charsets是否可接受,如果为true,则返回最佳匹配,否则为false .

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

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

如果未指定任何参数,则所有接受的字符集将作为数组返回:

// Accept-Charset: utf-8, utf-8;q=0.2, utf-7;q=0.5
ctx.acceptsCharsets();
// => ["utf-8", "utf-7", "utf-8"]

request.acceptsLanguages(langs)

检查langs是否可接受,如果为true,则返回最佳匹配,否则为false .

// Accept-Language: en;q=0.8, es, pt
ctx.acceptsLanguages('es', 'en');
// => "es"

ctx.acceptsLanguages(['en', 'es']);
// => "es"

如果未提供任何参数,则将所有接受的语言作为数组返回:

// Accept-Language: en;q=0.8, es, pt
ctx.acceptsLanguages();
// => ["es", "pt", "en"]

request.idempotent

检查请求是否是幂等的.

request.socket

返回请求套接字.

request.get(field)

返回请求标头.

Response

Koa Response对象是节点普通响应对象之上的抽象,提供了对每天HTTP服务器开发有用的附加功能.

API

response.header

响应头对象.

response.headers

响应头对象. 别名为response.header .

response.socket

请求套接字.

response.status

获取响应状态. 默认情况下, response.status设置为404与节点的res.statusCode默认为200 .

response.status=

通过数字代码设置响应状态:

注意 :不要太担心记住这些字符串,如果您有错字,将抛出错误,并显示此列表,以便您进行更正.

response.message

获取响应状态消息. 默认情况下, response.messageresponse.status关联.

response.message=

将响应状态消息设置为给定值.

response.length=

将响应内容长度设置为给定值.

response.length

返回响应Content-Length作为一个数字(如果存在),或者从ctx.body推导(如果可能),或者undefined .

response.body

获取响应正文.

response.body=

将响应主体设置为以下之一:

如果尚未设置response.status ,Koa将自动将状态设置为200204 .

Koa并不防备可能放入响应主体的所有内容-函数没有有意义的序列化,根据您的应用程序返回布尔值可能是有意义的,并且尽管错误起作用,但它可能无法像某些人预期的那样起作用错误的属性不可枚举. 我们建议您在应用中添加中间件,以声明每个应用的主体类型. 一个示例中间件可能是:

app.use(async (ctx, next) => {
  await next()

  ctx.assert.equal('object', typeof ctx, 500, 'some dev did something wrong')
})

String

Content-Type默认为text / html或text / plain,两者的默认字符集均为utf-8. 还设置了内容长度字段.

Buffer

Content-Type默认为application / octet-stream,并且Content-Length也已设置.

Stream

Content-Type默认为application / octet-stream.

只要将流设置为响应主体, .onerror就会自动作为侦听器添加到error事件以捕获任何错误. 此外,无论何时关闭请求(甚至过早),流都将被破坏. 如果不想使用这两个功能,请不要将流直接设置为主体. 例如,在将主体设置为代理中的HTTP流时,您可能不希望这样做,因为它将破坏基础连接.

参见: https : //github.com/koajs/koa/pull/612了解更多信息.

这是在不自动破坏流的情况下处理流错误的示例:

const PassThrough = require('stream').PassThrough;

app.use(async ctx => {
  ctx.body = someHTTPStream.on('error', ctx.onerror).pipe(PassThrough());
});

Object

Content-Type默认为application / json. 这包括普通对象{ foo: 'bar' }和数组['foo', 'bar'] .

response.get(field)

获取不区分大小写的field的响应头字段值.

const etag = ctx.response.get('ETag');

response.set(field, value)

将响应标头field设置为value

ctx.set('Cache-Control', 'no-cache');

response.append(field, value)

在附加头field中附加值val .

ctx.append('Link', '<http://127.0.0.1/>');

response.set(fields)

为一个对象设置几个响应头fields

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

这委托给setHeader ,它通过指定的键设置或更新标头,而不重置整个标头.

response.remove(field)

删除标题field .

response.type

获取没有参数(例如"字符集")的Content-Type响应.

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

response.type=

通过mime字符串或文件扩展名设置响应Content-Type .

ctx.type = 'text/plain; charset=utf-8';
ctx.type = 'image/png';
ctx.type = '.png';
ctx.type = 'png';

注意:在为您选择了适当的charset ,例如response.type = 'html'将默认为" utf-8". 如果需要覆盖charset ,请使用ctx.set('Content-Type', 'text/html')将响应标头字段直接设置为value.

response.is(types...)

ctx.request.is()非常相似. 检查响应类型是否为提供的类型之一. 这对于创建操纵响应的中间件特别有用.

例如,这是一个中间件,可最小化除流之外的所有HTML响应.

const minify = require('html-minifier');

app.use(async (ctx, next) => {
  await next();

  if (!ctx.response.is('html')) return;

  let body = ctx.body;
  if (!body || body.pipe) return;

  if (Buffer.isBuffer(body)) body = body.toString();
  ctx.body = minify(body);
});

response.redirect(url, [alt])

执行[302]重定向到url .

当不存在Referrer alt或使用" /"时,字符串" back"是特殊情况,以提供Referrer支持.

ctx.redirect('back');
ctx.redirect('back', '/index.html');
ctx.redirect('/login');
ctx.redirect('http://google.com');

要更改默认状态302 ,只需在此调用之前或之后分配状态即可. 要更改主体,请在此调用之后进行分配:

ctx.status = 301;
ctx.redirect('/cart');
ctx.body = 'Redirecting to shopping cart';

response.attachment([filename], [options])

Content-Disposition设置为" attachment",以指示客户端提示下载. (可选)指定下载filename和一些选项 .

response.headerSent

检查响应头是否已经发送. 对于查看客户端是否可能收到错误通知很有用.

response.lastModified

如果存在,则将Last-Modified头作为Date返回.

response.lastModified=

Last-Modified标头设置为适当的UTC字符串. 您可以将其设置为Date或日期字符串.

ctx.response.lastModified = new Date();

response.etag=

设置包含包装的"的响应的ETag.请注意,没有相应的response.etag getter.

ctx.response.etag = crypto.createHash('md5').update(ctx.body).digest('hex');

response.vary(field)

field变化.

response.flushHeaders()

冲洗所有设置的表头,然后开始执行正文.

Sponsor

由Koa的原始作者之一, Apex Ping是用于网站和API的漂亮的正常运行时间监视解决方案.

Links

社区链接,用于发现Koa的第三方中间件,完整的可运行示例,详尽的指南等! 如果您有任何疑问,请加入我们的IRC!

by  ICOPY.SITE