请求(Request)

Koa Request 对象是在 node 的 原生请求对象之上的抽象,提供了诸多对 HTTP 服务器开发有用的功能。

API

request.header

请求头对象。这与 node http.IncomingMessage 上的 headers 字段相同

request.header=

设置请求头对象。

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

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

request.href

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

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

request.path

获取请求路径名。

request.path=

设置请求路径名,并在存在时保留查询字符串。

request.querystring

根据 ? 获取原始查询字符串.

request.querystring=

设置原始查询字符串。

request.search

使用 ? 获取原始查询字符串。

request.search=

设置原始查询字符串。

request.host

存在时获取主机(hostname:port)。当 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, 不含 “charset” 等参数。

译者注: 这里其实是只获取 mime-type, 详见源码及其注释

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

request.charset

存在时获取请求字符集,或者 undefined

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

request.query

获取解析的查询字符串, 当没有查询字符串时,返回一个空对象。请注意,此 getter 支持嵌套解析。

例如 “color=blue&size=small”:

  1. {
  2. color: 'blue',
  3. size: 'small'
  4. }

request.query=

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

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

request.fresh

检查请求缓存是否“新鲜”,也就是内容没有改变。此方法用于 If-None-Match / ETag, 和 If-Modified-SinceLast-Modified 之间的缓存协商。 在设置一个或多个这些响应头后应该引用它。

  1. // 新鲜度检查需要状态20x或304
  2. ctx.status = 200;
  3. ctx.set('ETag', '123');
  4. // 缓存是好的
  5. if (ctx.fresh) {
  6. ctx.status = 304;
  7. return;
  8. }
  9. // 缓存是陈旧的
  10. // 获取新数据
  11. 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-Proto

request.ips

X-Forwarded-For 存在并且 app.proxy 被启用时,这些 ips 的数组被返回,从上游 - >下游排序。 禁用时返回一个空数组。

例如,如果值是 “client, proxy1, proxy2”,将会得到数组 ["client", "proxy1", "proxy2"]

大多数反向代理(nginx)都通过 proxy_add_x_forwarded_for 设置了 x-forwarded-for,这带来了一定的安全风险。恶意攻击者可以通过伪造 X-Forwarded-For 请求头来伪造客户端的ip地址。 客户端发送的请求具有 ‘forged’ 的 X-Forwarded-For 请求头。 在由反向代理转发之后,request.ips 将是 [‘forged’, ‘client’, ‘proxy1’, ‘proxy2’]。

Koa 提供了两种方式来避免被绕过。

如果您可以控制反向代理,则可以通过调整配置来避免绕过,或者使用 koa 提供的 app.proxyIpHeader 来避免读取 x-forwarded-for 获取 ips。

  1. const app = new Koa({
  2. proxy: true,
  3. proxyIpHeader: 'X-Real-IP',
  4. });

如果您确切知道服务器前面有多少个反向代理,则可以通过配置 app.maxIpsCount 来避免读取用户的伪造的请求头:

  1. const app = new Koa({
  2. proxy: true,
  3. maxIpsCount: 1, // 服务器前只有一个代理
  4. });
  5. // request.header['X-Forwarded-For'] === [ '127.0.0.1', '127.0.0.2' ];
  6. // ctx.ips === [ '127.0.0.2' ];

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。 如果没有请求主体,返回 null。 如果没有内容类型,或者匹配失败,则返回 false。 反之则返回匹配的 content-type。

  1. // 使用 Content-Type: text/html; charset=utf-8
  2. ctx.is('html'); // => 'html'
  3. ctx.is('text/html'); // => 'text/html'
  4. ctx.is('text/*', 'text/html'); // => 'text/html'
  5. // 当 Content-Type 是 application/json 时
  6. ctx.is('json', 'urlencoded'); // => 'json'
  7. ctx.is('application/json'); // => 'application/json'
  8. ctx.is('html', 'application/*'); // => 'application/json'
  9. ctx.is('html'); // => false

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

  1. if (ctx.is('image/*')) {
  2. // 处理
  3. } else {
  4. ctx.throw(415, 'images only!');
  5. }

内容协商

Koa 的 request 对象包括由 acceptsnegotiator 提供的内容协商实用函数。

这些实用函数是:

  • request.accepts(types)
  • request.acceptsEncodings(types)
  • request.acceptsCharsets(charsets)
  • request.acceptsLanguages(langs)

如果没有提供类型,则返回 所有 可接受的类型。

如果提供多种类型,将返回最佳匹配。 如果没有找到匹配项,则返回一个false,你应该向客户端发送一个406 "Not Acceptable" 响应。

如果接收到任何类型的接收头,则会返回第一个类型。 因此,你提供的类型的顺序很重要。

request.accepts(types)

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

  1. // Accept: text/html
  2. ctx.accepts('html');
  3. // => "html"
  4. // Accept: text/*, application/json
  5. ctx.accepts('html');
  6. // => "html"
  7. ctx.accepts('text/html');
  8. // => "text/html"
  9. ctx.accepts('json', 'text');
  10. // => "json"
  11. ctx.accepts('application/json');
  12. // => "application/json"
  13. // Accept: text/*, application/json
  14. ctx.accepts('image/png');
  15. ctx.accepts('png');
  16. // => false
  17. // Accept: text/*;q=.5, application/json
  18. ctx.accepts(['html', 'json']);
  19. ctx.accepts('html', 'json');
  20. // => "json"
  21. // No Accept header
  22. ctx.accepts('html', 'json');
  23. // => "html"
  24. ctx.accepts('json', 'html');
  25. // => "json"

你可以根据需要多次调用 ctx.accepts(),或使用 switch:

  1. switch (ctx.accepts('json', 'html', 'text')) {
  2. case 'json': break;
  3. case 'html': break;
  4. case 'text': break;
  5. default: ctx.throw(406, 'json, html, or text only');
  6. }

request.acceptsEncodings(encodings)

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

  1. // Accept-Encoding: gzip
  2. ctx.acceptsEncodings('gzip', 'deflate', 'identity');
  3. // => "gzip"
  4. ctx.acceptsEncodings(['gzip', 'deflate', 'identity']);
  5. // => "gzip"

当没有给出参数时,所有接受的编码将作为数组返回:

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

请注意,如果客户端显式地发送 identity;q=0,那么 identity 编码(这意味着没有编码)可能是不可接受的。 虽然这是一个边缘的情况,你仍然应该处理这种方法返回 false 的情况。

request.acceptsCharsets(charsets)

检查 charsets 是否可以接受,在 true 时返回最佳匹配,否则为 false

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

当没有参数被赋予所有被接受的字符集将作为数组返回:

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

request.acceptsLanguages(langs)

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

  1. // Accept-Language: en;q=0.8, es, pt
  2. ctx.acceptsLanguages('es', 'en');
  3. // => "es"
  4. ctx.acceptsLanguages(['en', 'es']);
  5. // => "es"

当没有参数被赋予所有接受的语言将作为数组返回:

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

request.idempotent

检查请求是否是幂等的。

request.socket

返回请求套接字。

request.get(field)

返回请求头(header), field 不区分大小写.