koa简介
官网简介:koa是基于Node.js平台的下一代web开发框架
源码学习
源码lib文件目录:
├── application.js # 入口文件
├── context.js # 创建网络请求的上下文对象
├── request.js # koa的request对象
└── response.js # koa的response对象
koa-compose
用法:
使用app.use可以将给定的中间件添加到应用程序,作为参数的函数(就是一个中间件)接收两个参数:ctx和next,next也是一个函数
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log('第一个中间件函数')
await next();
console.log('第一个中间件函数next之后!');
})
app.use(async (ctx, next) => {
console.log('第二个中间件函数')
await next();
console.log('第二个中间件函数next之后!');
})
app.listen(3000);
//运行结果是
第一个中间件函数
第二个中间件函数
第二个中间件函数next之后!
第一个中间件函数next之后!
//结合下面源码
一个中间件函数其实就是上面的async (ctx,next)=>{ await next()},首先执行的dispatch(0),
就是第一个中间件函数,执行完“第一个中间件函数”,后面await next(),开始执行第二个中间件函数
,“第二个中间件函数”执行完后,await next(),返回的的Promise resolve(),之后开始执行“第二
个中间函数next之后”,然后第一个中间件的await结束了,开始执行“第一个中间件函数next之后!”,
最后执行完毕
源码:
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
function compose (middleware) {
//参数是一个数组,数组的每一项是一个函数
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
// last called middleware #
// index初始值为-1,因为第一次调用下标是0,同时需要判断index是否大于i,如果为0会和下面判断逻辑冲突
let index = -1
//调用dispatch()返回的结果是一个Promise
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
//真正resolve的是这一句,fn是中间件数组第i个,resolve又继续调用第i+1个,依次调用知道最后一个
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
总结:compose对中间件的处理有点类似于递归的思想,首先是将中间件数组middleware传入,按顺序从第一个开始执行,一遇到next(),就执行数组中的下一个中间件,等所有中间件都执行后,在继续执行next()之后的代码,从最后一个中间件一直执行到第一个。
Application.js
koa的简单用法:
const Koa = require( 'koa');
const app = new Koa();
app.use( async (ctx,next) => {
ctx.body = 'Hello World';
} );
app.listen( 3000 );
koa的启动流程主要就是四个步骤:引入koa包–>实例化koa对象–>编写中间件–>监听服务
Application.js源码
const Emitter = require('events')
//使用koa时引入的koa包其实就是Application类,继承于Node的events类
module.exports = class Application extends Emitter {
constructor (options) {
super()
options = options || {}
//poxy是代理设置,为true时表示获取真正的客户端ip地址
this.proxy = options.proxy || false
//子域名偏移量
this.subdomainOffset = options.subdomainOffset || 2
this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For'
this.maxIpsCount = options.maxIpsCount || 0
this.env = options.env || process.env.NODE_ENV || 'development'
this.compose = options.compose || compose
if (options.keys) this.keys = options.keys
//中间件函数队列
this.middleware = []
//context.js、request.js、response.js创建的对象
this.context = Object.create(context)
this.request = Object.create(request)
this.response = Object.create(response)
// util.inspect.custom support for node 6+
/* istanbul ignore else */
if (util.inspect.custom) {
this[util.inspect.custom] = this.inspect
}
}
//listen函数创建了一个原生的http服务,并监听传入的端口号
listen (...args) {
debug('listen')
//this.callback是对响应内容做一些处理的函数
const server = http.createServer(this.callback())
return server.listen(...args)
}
toJSON () {
return only(this, [
'subdomainOffset',
'proxy',
'env'
])
}
inspect () {
return this.toJSON()
}
//use是用来使用中间件函数的,fn即为中间件
use (fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!')
debug('use %s', fn._name || fn.name || '-')
this.middleware.push(fn)
return this
}
//compose来源于koa-compose包,用来处理中间件函数
callback () {
const fn = this.compose(this.middleware)
if (!this.listenerCount('error')) this.on('error', this.onerror)
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res)
return this.handleRequest(ctx, fn)
}
return handleRequest
}
//执行中间件函数,且一开始就将res的statusCode定义为404。如果在我们没有设置body的情况下,默认就会返回404。
handleRequest (ctx, fnMiddleware) {
const res = ctx.res
res.statusCode = 404
const onerror = err => ctx.onerror(err)
const handleResponse = () => respond(ctx)
onFinished(res, onerror)
return fnMiddleware(ctx).then(handleResponse).catch(onerror)
}
//调用object.create()对createServer返回的req、res进行封装,创建一个新的上下文
//对象ctx,每次访问ctx对象都是独立的对象
createContext (req, res) {
const context = Object.create(this.context)
const request = context.request = Object.create(this.request)
const response = context.response = Object.create(this.response)
context.app = request.app = response.app = this
context.req = request.req = response.req = req
context.res = request.res = response.res = res
request.ctx = response.ctx = context
request.response = response
response.request = request
context.originalUrl = request.originalUrl = req.url
context.state = {}
return context
}
}
总结:Application类完整的流程是,使用use函数时,会将中间件函数push进中间件数组中,通过compose函数决定执行中间件函数的顺序,将创建好的ctx对象和排好顺序的中间件函数传入handleRequest转化成对应的响应体,再将此传入http服务的listen函数监听执行
Context.js
context.js有一个比较特殊的地方就是通过delegate库来代理函数及对象,比如调用ctx.hostname本质则是调用了req.hostname。通过delegate代理到context对应函数方法或对象上。
/**
* Response delegation.
*/
delegate(proto, 'response')
.method('attachment')
.method('redirect')
.method('remove')
.method('vary')
.method('has')
.method('set')
.method('append')
.method('flushHeaders')
.access('status')
.access('message')
.access('body')
.access('length')
.access('type')
.access('lastModified')
.access('etag')
.getter('headerSent')
.getter('writable')
/**
* Request delegation.
*/
delegate(proto, 'request')
.method('acceptsLanguages')
.method('acceptsEncodings')
.method('acceptsCharsets')
.method('accepts')
.method('get')
.method('is')
.access('querystring')
.access('idempotent')
.access('socket')
.access('search')
.access('method')
.access('query')
.access('path')
.access('url')
.access('accept')
.getter('origin')
.getter('href')
.getter('subdomains')
.getter('protocol')
.getter('host')
.getter('hostname')
.getter('URL')
.getter('header')
.getter('headers')
.getter('secure')
.getter('stale')
.getter('fresh')
.getter('ips')
.getter('ip')
Request.js和Response.js
request和response对象就没有什么特别的了,就是一些get,set方法获取header、修改status等等。
module.exports = {
get socket () {
return this.res.socket
},
get header () {
const { res } = this
return typeof res.getHeaders === 'function'
? res.getHeaders()
: res._headers || {} // Node < 7.7
},
get headers () {
return this.header
},
get status () {
return this.res.statusCode
},
set status (code) {
if (this.headerSent) return
assert(Number.isInteger(code), 'status code must be a number')
assert(code >= 100 && code <= 999, `invalid status code: ${code}`)
this._explicitStatus = true
this.res.statusCode = code
if (this.req.httpVersionMajor < 2) this.res.statusMessage = statuses[code]
if (this.body && statuses.empty[code]) this.body = null
},
get message () {
return this.res.statusMessage || statuses[this.status]
},
set message (msg) {
this.res.statusMessage = msg
},
get body () {
return this._body
},
}