别催了~ 正在从服务器偷取页面 . . .

koa源码学习


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
  },
}


文章作者: John Doe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 John Doe !
评论
  目录