您现在的位置:首页 >> 前端 >> 内容

koa2源码学习

时间:2017/8/17 9:13:00 点击:

  核心提示:koa是基于 nodejs 的一个轻量级框架,由 express 原班人马打造。最新工作用到了该框架,所以学习了一下它的源码。代码量不多,只有千行多,但是非常精悍。1、安装 koa$ npm inst...

koa是基于 nodejs 的一个轻量级框架,由 express 原班人马打造。最新工作用到了该框架,所以学习了一下它的源码。代码量不多,只有千行多,但是非常精悍。

1、安装 koa

$ npm install -g n
$ n 0.11.12
$ node --harmony my-koa-app.js

使用

var koa = require('koa');
var app = koa();

app.use(function *(){
  this.body = 'Hello World';
});

app.listen(3000);

可以看出来非常简单,适合新手和需要快速入门的同学。

2、代码概览

  • 下载代码git clone https://github.com/koajs/koa.git
    • 执行npm install

      koa2源码学习

      从源码中可以看到四个文件:
      application:负责对外的所有接口
      context:上下文,可以获取request和response,并对其进行操作
      request:client 发起的请求
      response:响应信息

      2.1 、application.js

      2.1.1、监听端口

      listen(...args) {
          debug('listen');
          const server = http.createServer(this.callback());
          return server.listen(...args);
        }

      是对node的一层封装,更方便的去使用。
      监听接口可以传入多个回调处理请求。中间件就是在callback中合成的。

      2.1.2中间件处理

      a.添加中间件
      use 函数:this.middleware.push(fn);会将处理函数放入数组

      b.合成中间件

      return Promise.resolve(fn(context, function next () {
        return dispatch(i + 1)
      }))

      返回了一个promise对象,一个一个的倒序执行中间件

      c.执行

      const handleRequest = (req,res) => {
          res.statusCode = 404;
          const ctx = this.createContext(req,res);
          const onerror = err => ctx.onerror(err);
          const handleRespon = () => respond(ctx);
          onFinished(res, onerror);
      
          return fn(ctx).then(handleResponse).catch(onerror);
      }

      fn就是promise函数

      2.1.3、初始化的属性

      this.proxy = false;//是否是代理
      this.middleware = [];//保存中间件
      this.subdomainOffset = 2;//子域名偏移量
      this.env = process.env.NODE_ENV || ‘development’;//当前开发环境
      this.context = Object.create(context);//上下文
      this.request = Object.create(request);
      this.response = Object.create(response);

      注意使用了Object.create(parent):
      他有三个功能: 1. 创建一个对象
      2. 继承指定父对象
      3. 为新对象扩展新属性
      何时使用create: 希望在创建对象时就提前指定继承的父对象,并同时扩展新属性时。

      2.1.4 、初始化上下文

      createContext(req, res)

      context,request,reponse都持有req,res和app,三者又相互持有
      context单独持有了originalUrl,cookies,accept和state。

      2.1.5、错误处理

      onerror(err)
      如果是404或者需要exposemsg 则返回
      如果有client标记则返回
      有堆栈则返回堆栈信息,没有则将err转换为string打印处理

      2.1.6、respond

      执行完中间件后会执行handleresponse方法,这里用来结束请求,告诉客户端所有消息已经发送。当所有要返回的内容发送完毕时,该函数必须被调用一次。如何不调用该函数,客户端将永远处于等待状态。
      a. respond = false:则不通过koa设置返回的body信息
      b. ctx.writable ==false则表示请求头已发送,直接返回
      c. http 204 205 304 表示body没有信息直接返回即可
      204代表响应报文中包含若干首部和一个状态行,但是没有实体的主体内容
      205则是告知浏览器清除当前页面中的所有html表单元素,也就是表单重置。
      304如果客户端发送的是一个条件验证(Conditional Validation)请求,则web服务器可能会返回HTTP/304响应,这就表明了客户端中所请求资源的缓存仍然是有效的,也就是说该资源从上次缓存到现在并没有被修改过.条件请求可以在确保客户端的资源是最新的同时避免因每次都请求完整资源给服务器带来的性能问题.

      d.’HEAD’ == ctx.method 只请求,不需要加body信息,直接结束

      e.如果 body 是 错误信息,string,json,stream等都要在response中打印出来。

      2.2、context.js

      2.2.1、属性简介

      context上下文持有了这些属性:request 、response、app
      、originalUrl: this.originalUrl、req: (original node req)、res: (original node res)、socket(original node socket)

      为了方便操作其中的request和response,里面还加了代理方法,将context设置为了request和response一些方法的代理 里面核心是调用apply方法。

        proto[name] = function(){
          return this[target][name].apply(this[target], arguments);
        };

      2.2.2、context onerror 错误处理

      a.没有错误则直接返回
      b.如果头部信息已发送
      this.app.emit(‘error’, err, this);,则由app处理错误
      c.没有发送,则清空头部信息
      e.设置为text
      f.如果是以下这个错误,则返回404

      ENOENT  No such file or directory 
      

      其他默认500错误
      g、如果允许则展示错误信息,否则展示以上错误码

      2.3、request.js response.js

      2.3.1设置头部信息

      Accept:image/gif.image/jpeg.*/*
      Accept-Language:zh-cn
      Connection:Keep-Alive
      Host:localhost
      User-Agent:Mozila/4.0(compatible:MSIE5.01:Windows NT5.0)
      Accept-Encoding:gzip,deflate.

      get 和set可以设置和获取头部信息

      其他如url等可以参考下面PHP的例子

      select parse_url('https://facebook.com/path/p1.php?query=1', 'PROTOCOL') from dual;   --http
      
      select parse_url('https://facebook.com/path/p1.php?query=1', 'HOST') from dual;---facebook.com
      
      select parse_url('https://facebook.com/path/p1.php?query=1', 'REF') from dual;---空
      
      select parse_url('https://facebook.com/path/p1.php?query=1', 'PATH') from dual;---/path/p1.php
      
      select parse_url('https://facebook.com/path/p1.php?query=1', 'QUERY') from dual;---空
      select parse_url('https://facebook.com/path/p1.php?query=1', 'FILE') from dual;---/path/p1.php?query=1
      select parse_url('https://facebook.com/path/p1.php?query=1', 'AUTHORITY') from dual;---facebook.com
      select parse_url('https://facebook.com/path/p1.php?query=1', 'USERINFO') from dual;---空

      URL即:统一资源定位符 (Uniform Resource Locator, URL)
      完整的URL由这几个部分构成:
      scheme://host:port/path?query#fragment
      scheme:通信协议
      常用的http,ftp,maito等

      host:主机
      服务器(计算机)域名系统 (DNS) 主机名或 IP 地址。

      port:端口号
      整数,可选,省略时使用方案的默认端口,如http的默认端口为80。

      path:路径
      由零或多个”/”符号隔开的字符串,一般用来表示主机上的一个目录或文件地址。

      query:查询
      可选,用于给动态网页(如使用CGI、ISAPI、php/JSP/ASP/ASP.NET等技术制作的网页)传递参数,可有多个参数,用”&”符号隔开,每个参数的名和值用”=”符号隔开。

      fragment:信息片断
      字符串,用于指定网络资源中的片断。例如一个网页中有多个名词解释,可使用fragment直接定位到某一名词解释。(也称为锚点.)

      值得一提的是response中有etag等方法,这些适用于设置浏览器缓存的,在浏览器优化体验上有很大作用。详情参考这篇文章

      总结

      1.其他组件采用中间件的方式接入,使得框架非常轻量级。
      2.response,request,上下文等解耦合理,结构非常清晰。

      3.麻雀虽小五脏俱全,读源码能学到这个框架的精妙的思想。

Tags:KO OA A2 2源 
作者:网络 来源:hiphopC的博客