阅读学习Koa Core源码,基于koa v2.5.2。

Koa的核心代码很少,就四个文件application, context, request, response,算上注释和空行目前也还没过2000行代码。

这一篇针对request, response的源码进行阅读学习。

Koa Core

koajs/koa

  • lib/application
  • lib/context
  • lib/request
  • lib/response

主要这四个文件,当然也还依赖了很多外部库,以及koa的其他仓库。这一篇看后两部分lib/request, lib/response

这两部分代码行数比较多,就不把所有代码贴出来了。

Request

Request Socket

socket()

获取请求对应的socket对象,即req.socket

Request URL 相关

url(), origin(), href(), path(), query(), querystring(), search(), host(), hostname(), URL(), subdomains(), protocol(), secure()

URL相关的一系列方法,用于解析、获取URL中的相关信息。

用到的库包括:querystring, parseurl, url.format, url.URL

注意区分url()URL():

  • url()直接对应的是req.url
  • URL()是通过url.URL类,将protocol(), host(), req.url拼接在一起得到的URL对象

Request Header 获取

header(), headers(), get()

header()headers()是等价的别名,针对request的headers信息,设置相应的getter和setter。只有get header()get headers()被代理到了ctx上。

get()是一个方法,用于获取header中的字段。

例如:request.header['Content-Type'] 等价于 request.get('Content-Type')

Request Header 相关

通过请求中的各个headers,获取相关的内容:

  • method(): 获取请求的method。

  • idempotent(): 判断请求是否是幂等的,主要是通过请求的方法是否是'GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE'之一来判断。

  • fresh(): 判断请求的内容是否未过期,就是判断请求的Last-ModifiedEtag是否匹配,通过第三方库fresh实现。

  • stale(): 判断请求的内容是否已过期,即!fresh

  • charset(): 获取请求的Content-Type中的charset,通过第三方库content-type辅助实现。

  • length(): 获取请求的Content-Length

  • ips(): 如果请求经过了反向代理,获取请求的X-Forwarded-For中的ip数组。

  • ip(): 如果请求经过了反向代理,获取请求的X-Forwarded-For中的ip数组的第一个ip。如果没有经过反向代理,返回socket.remoteAddress

  • is(): 判断请求的Content-Type是否是某种或某些类型,通过第三方库type-is实现。

  • type(): 获取请求的Content-Type

  • accept(): 解析请求的Accept-*一系列headers。通过第三方库accepts实现,返回该库的一个Accept对象。

  • accepts(): 即accept.types方法,判断请求的Content-Type是否匹配某种或某些类型。返回传入的最匹配的类型,如果均不匹配返回false

  • acceptsCharsets(): 即accept.charsets方法,判断Accept-Charset

  • acceptsLanguages(): 即accept.languages方法,判断Accept-Language

  • acceptsEncodings(): 即accept.encodings方法,判断Accept-Encoding

Request 其它方法

inspect(), toJSON()

Request的inspect方法。

Response

Response Socket

socket()

获取响应对应的socket对象,即res.socket

Response Header 设置

header()headers()是等价的别名,针对response的headers信息,仅有getter,而没有setter。get()是一个方法,用于获取header中的字段。这三个与request中对应的方法相同,但注意均没有被代理到ctx上。

set()是一个方法,用于设置响应中对应的header字段。

append()方法,用于在相应的header字段中添加内容。和set()的区别在于,如果对应字段已存在,set()是直接替换相应内容,而append()是以数组形式追加相应内容。例如:

response.set('Foo', 'bar')
response.set('Foo', 'baz') // -> Foo: baz

response.set('Fo', 'bar')
response.append('Fo', 'baz') // -> Fo: bar; baz

remove()方法则是从header中移除相应字段。

set(), append(), remove()均被代理到了ctx上。

Response Header 相关

  • length(): 用于获取和设置响应的Content-Length,若没有手动设置过,则自动计算body的长度。
  • vary(): 用于设置响应的Vary,通过第三方库vary实现。
  • lastModified(): 用于获取和设置响应的Last-Modified
  • eTag(): 用于获取和设置响应的ETag
  • type(): 用于获取和设置响应的Content-Type,通过第三方库cache-content-type来辅助实现。
  • is(): 用于判断响应的Content-Type是否属于某些类型,通过第三方库type-is来辅助实现。
  • attachment(): 用于设置响应的Content-Dispositionattachment,同时可以传入filename。如果传入filename,则会通过path.extname判断扩展名,自动设置相应的Content-Type。通过第三方库content-disposition来辅助实现。
  • redirect(): 用于设置响应的Location,同时将status设置为302(除非手动设置为301)。如果请求的Referrer存在,可以通过传入'back'跳转回之前页面。同时根据请求的Accepts预设了返回的body内容和相应的Content-Type.

Response 具体响应

  • status(): 获取和设置响应的HTTP状态码,并将请求的messagebody设置为相应的信息。通过第三方库statuses来判断状态码是否合法,并获取对应状态码的状态信息。
  • message(): 获取和设置响应的HTTP状态码信息。如果使用的HTTP版本低于2.0,将会根据status()自动设置(HTTP2.0中不再包含状态信息,只包含状态码)。
  • body(): 获取和设置响应的主体,并根据传入的类型自动设置Content-Type, Content-Length等相关headers,以及设置状态码为200 OK204 No Content

注意这三个方法会彼此调用,一般只设置status()或只设置body()即可。

Response 状态

  • headerSent(): 判断响应的headers的内容是否已经写入socket,如果已经写入了,上述所有修改headers的方法都将失效,直接return。
  • writable(): 判断响应的socket是否可写,如果响应已结束或者socket已关闭则不可写。
  • flushHeaders(): 立即将现在已经设置好的headers发送,并开始body部分。

Response 其它方法

inspect(), toJSON()

Response的inspect方法。

More

Koa对request和response的别名,以及在context上的代理,其实并不直观,不太符合语义上的直觉。

很早就有一个Issue(Request / response aliases are bad idea #849)提出过这个问题,里面的讨论有些还是挺有意思的。

其实了解了代码的思想之后,这些就是使用习惯上的问题。

Koa的核心代码其实很短和好理解,还有很多就是中间件、第三方库的内容了。下一篇会将koa-compose过一遍,看看koa是怎么把多个中间件按顺序合成一个的。

References

Koa源码阅读:
Koa Core - 源码阅读 1 - Application
Koa Core - 源码阅读 2 - Context