阅读学习Koa Core源码,基于koa v2.5.2。
Koa的核心代码很少,就四个文件application, context, request, response,算上注释和空行目前也还没过2000行代码。
这一篇针对request, response的源码进行阅读学习。
# Koa Core
lib/applicationlib/contextlib/requestlib/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.urlURL()是通过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-Modified和Etag是否匹配,通过第三方库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-Disposition为attachment,同时可以传入filename。如果传入filename,则会通过path.extname判断扩展名,自动设置相应的Content-Type。通过第三方库content-disposition来辅助实现。redirect(): 用于设置响应的Location,同时将status设置为302(除非手动设置为301)。如果请求的Referrer存在,可以通过传入'back'跳转回之前页面。同时根据请求的Accepts预设了返回的body内容和相应的Content-Type.
# Response 具体响应
status(): 获取和设置响应的HTTP状态码,并将请求的message和body设置为相应的信息。通过第三方库statuses来判断状态码是否合法,并获取对应状态码的状态信息。message(): 获取和设置响应的HTTP状态码信息。如果使用的HTTP版本低于2.0,将会根据status()自动设置(HTTP2.0中不再包含状态信息,只包含状态码)。body(): 获取和设置响应的主体,并根据传入的类型自动设置Content-Type,Content-Length等相关headers,以及设置状态码为200 OK或204 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
# Related posts
Koa源码阅读:
Koa Core - 源码阅读 1 - Application
Koa Core - 源码阅读 2 - Context