此文已由作者张佃鹏授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。

Koa 就是一种简单好用的 Web 框架。它的特点是优雅、简洁、表达力强、自由度高。本身代码只有1000多行。koa一个中间件框架,其提供的是一个架子,而几乎所有的功能都需要由第三方中间件完成,它只是node原生的http的一个封装,再加入中间件元素,koa 不在内核方法中绑定任何中间件, 它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手

Koa目前分为两个版本:koa 1.0和koa2

  • koa 1.0: 依赖generator函数和Promise实现异步处理(ES6)

  • koa2: 依赖async函数和Promise实现异步处理(ES7)

以下的关于koa的介绍主要在koa2的基础上进行分析:

koa框架的使用

koa框架主要由以下几个元素组成:

app

const Koa = require('koa');const app = new Koa();

app的主要属性如下:

  • proxy: 表示是否开启代理信任开关,默认为false,如果开启代理信任,对于获取request请求中的host,protocol,ip分别优先从Header字段中的X-Forwarded-Host,X-Forwarded-Proto,X-Forwarded-For获取:

//以下是koa获取request对象部分属性的源码,都是由app.proxy属性决定的:{
    get ips() {        const proxy = this.app.proxy;        const val = this.get('X-Forwarded-For');        return proxy && val
          ? val.split(/\s*,\s*/)
          : [];
    },     get host() {        const proxy = this.app.proxy;        let host = proxy && this.get('X-Forwarded-Host');
        host = host || this.get('Host');        if (!host) return '';        return host.split(/\s*,\s*/)[0];
    },     get protocol() {        const proxy = this.app.proxy;        if (this.socket.encrypted) return 'https';        if (!proxy) return 'http';        const proto = this.get('X-Forwarded-Proto') || 'http';        return proto.split(/\s*,\s*/)[0];
    },
    get URL() {        if (!this.memoizedURL) {          const protocol = this.protocol;          const host = this.host;          const originalUrl = this.originalUrl || ''; // originalUrl为req.url
          try {            this.memoizedURL = new URL(`${protocol}://${host}${originalUrl}`);
          } catch (err) {            this.memoizedURL = Object.create(null);
          }
        }        return this.memoizedURL;
    },
    get hostname() {        const host = this.host;        if (!host) return '';        if ('[' == host[0]) return this.URL.hostname || ''; // IPv6
        return host.split(':')[0];
    },
}
  • env:node运行环境

this.env = process.env.NODE_ENV || 'development';
  • keys: app.keys是一个设置签名的Cookie密钥的数组,用于生成cookies对象

  • subdomainOffset:表示子域名是从第几级开始的,这个参数决定了request.subdomains的返回结果,默认值为2

//比如有netease.youdata.163.com域名app.subdomainOffset = 2;console.log(ctx.request.subdomains);  //返回["youdata", "netease"]app.subdomainOffset = 3;console.log(ctx.request.subdomains);  //返回["netease"]//koa获取subdomains的源码get subdomains() {    const offset = this.app.subdomainOffset;    const hostname = this.hostname;    if (net.isIP(hostname)) return [];    return hostname
      .split('.')
      .reverse()
      .slice(offset);
},
  • middleware:app对应的中间件数组,使用app.use函数会将会将中间件加到该数组中

koa使用中间件方式来实现不同功能的级联,当一个中间件调用next(),则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为,类似一个入栈出栈的模式,中间件的使用方式如下:

const Koa = require('koa');const app = new Koa();
app.use((ctx, next) => {    console.log('step1-begin');
    next();    console.log('step1-end');
});
app.use((ctx, next) => {    console.log('step2-begin');
    next();    console.log('step2-end');
}); app.listen(3000);/*输出结果为:
    step1-begin
    step2-begin
    step2-end
    step1-end
*/
  • context:这个是创建中间件中使用的“ctx”的原型,直接使用app.context意义不大,而且app.context上很多属性其实是为ctx准备的,直接用app.context调用会报错:

//以下context.js中的部分源码:toJSON() {    return {
      request: this.request.toJSON(),   //如果直接使用app.context调用这个会报错,因为这个时候this.request是undefined,只有在中间件里使用ctx调用才不会报错
      response: this.response.toJSON(),
      app: this.app.toJSON(),
      originalUrl: this.originalUrl,
      req: '<original node req>',
      res: '<original node res>',
      socket: '<original node socket>'
    };
  },

context主要有以下用途:

//我们可以在context对象上加一些全局路由里公用的属性,这样就不需要每次请求都在中间件里赋值const Koa = require('koa');const app = new Koa();
app.context.datasourceConfig = {    "connectionLimit": 100,    "database": "development",    "host": "10.165.124.134",    "port": 3360,    "user": "sup_bigviz",    "password": "123456",    "multipleStatements": true};
app.use((ctx, next) => {    console.log('datasourceConfig:', ctx.datasourceConfig); //这里可以打印出全局配置
    next();
});
  • request: 这个是创建ctx.request的原型,直接使用app.context.request几乎没有意义,很多属性都会报错,不过和app.context一样,可以给app.context添加一些ctx.request中用到的公共属性

  • response: 这个是创建ctx.response的原型,直接使用app.context.response几乎没有意义,很多属性都会报错,不过和app.context一样,可以给app.context添加一些ctx.request中用到的公共属性

app的主要函数如下:

  • use函数: use函数主要作用是给app.middleware数组中添加中间件

let koa = require('koa');
koa.use(async (ctx, next) => {    //before do something...    next();    //after await do something...
})
  • listen函数:app.listen函数是创建服务的入口,只有调用app.listen函数以后,所有的中间件才会被使用

//app.listen其实是http.createServer的语法糖,源码实现如下:function listen(...args) {
    debug('listen');    const server = http.createServer(this.callback()); //最终所有路由处理是在app..callback中实现的
    return server.listen(...args);
 }
  • callback函数:返回一个函数供http.createServer() 方法的回调函数来处理请求。你也可以使用此回调函数将koa应用程序挂载到Connect/Express应用程序中

//koa的callback函数实现源码function callback() {    const fn = compose(this.middleware);   //koa-compose包负责讲多个中间件组装成一个中间件
    if (!this.listeners('error').length) this.on('error', this.onerror);    const handleRequest = (req, res) => {      const ctx = this.createContext(req, res);  //这个函数负责生成中间件接收器ctx,绑定一些对象的关联关系
      return this.handleRequest(ctx, fn);  //使用中间件函数fn处理路由请求
    };    return handleRequest;
}//handleRequest函数的源码实现也很简单,执行中间件函数,并做一些返回处理和异常处理function 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);
  }

ctx

ctx是中间件中的上下文环境,也是koa框架中最常用最重要的对象,每个请求都会根据app.context创建一个新的ctx,并在中间件中作为接收器引用

ctx对象上会绑定app,request,response等对象

//生成ctx的源码function createContext(req, res) {    const context = Object.create(this.context);   //由上文中讲解的app.context生成
    const request = context.request = Object.create(this.request);  //由上文中讲解的app.request生成
    const response = context.response = Object.create(this.response); //由上文中讲解的app.response生成
    context.app = request.app = response.app = this;
    context.req = request.req = response.req = req;   //req是node的req,尽量避免使用,而是使用ctx.request;
    context.res = request.res = response.res = res;   //res是node的res,尽量避免使用,而是应该使用ctx.response;
    request.ctx = response.ctx = context;
    request.response = response;
    response.request = request;
    context.originalUrl = request.originalUrl = req.url;
    context.cookies = new Cookies(req, res, {       //生成cookies,是由[cookie模块生成的](https://github.com/pillarjs/cookies):
      keys: this.keys,
      secure: request.secure   //secure是根据域名是不是https返回的结果
    });
    request.ip = request.ips[0] || req.socket.remoteAddress || '';   //客户端访问ip
    context.accept = request.accept = accepts(req);  //
    context.state = {};   //这个给用户使用,用于存放用户在多个中间件中用到的一些属性或者函数
    return context;
}

ctx会代理ctx.response和ctx.request上的一些属性和函数(这个代理逻辑是在ctx.response和ctx.request的原型上实现的)

//以下是koa源码(method表示代理方法,access表示代理属性可读可写,getter表示代理属性可读):delegate(proto, 'response')
  .method('attachment') //将Content-Disposition 设置为 “附件” 以指示客户端提示下载
  .method('redirect') //返回重定向,如果没有code设置,默认设置code为302
  .method('remove')   //删除响应头的某个属性
  .method('vary')  //设置Vary响应头
  .method('set') //设置响应头,可以传递对象,数组,单个值的形式
  .method('append') //给response.headers中的某个key值追加其它value
  .method('flushHeaders')  //执行this.res.flushHeaders()
  .access('status')  //http返回code码,优先选择用户的设置,如果用户没有主动设置,而设置了ctx.body的值, 如果设置值为null,则返回204,如果设置值不为null,那么返回200,否则默认情况下是404
  .access('message')  //获取响应的状态消息. 默认情况下, response.message 与 response.status 关联
  .access('body')   //response的返回结果
  .access('length')  //response的headers的Content-Length,可以自己设置,默认根据body二进制大小设置
  .access('type')   //设置响应的content-type
  .access('lastModified')  //设置响应头Last-Modified
  .access('etag')  //设置包含 " 包裹的 ETag 响应头
  .getter('headerSent')  //检查是否已经发送了一个响应头。 用于查看客户端是否可能会收到错误通知
  .getter('writable');   //返回是否可以继续写入delegate(proto, 'request')
  .method('acceptsLanguages')
  .method('acceptsEncodings')
  .method('acceptsCharsets')
  .method('accepts')        //accepts函数用于判断客户端请求是否接受某种返回类型
  .method('get')   //获取请求头中的某个属性值
  .method('is')  //判断请求头希望返回什么类型
  .access('querystring') //获取原始查询字符串
  .access('idempotent')
  .access('socket') //返回请求套接字
  .access('search') //搜索字符串
  .access('method')  //请求方法
  .access('query')  //获取请求的查询字符串对象
  .access('path')  //获取请求路径名
  .access('url')  //请求的url,该url可以被重写
  .getter('origin')  //获取url的来源:包括 protocol 和 host(http://example.com)
  .getter('href') //获取完整的请求URL,包括 protocol,host 和 url(http://example.com/foo/bar?q=1)
  .getter('subdomains') //获取请求的子域名
  .getter('protocol') //返回请求协议
  .getter('host') //获取当前主机的host(hostname:port)
  .getter('hostname') //获取当前主机的host
  .getter('URL') //获取 WHATWG 解析的 URL 对象
  .getter('header') //返回请求头对象
  .getter('headers')  //返回请求头对象
  .getter('secure') //通过 ctx.protocol == "https" 来检查请求是否通过 TLS 发出
  .getter('stale')
  .getter('fresh')
  .getter('ips')  //当 X-Forwarded-For 存在并且 app.proxy 被启用时,这些 ips 的数组被返回
  .getter('ip'); //请求远程地址//比如以下操作是等价的:ctx.body = {
    code: 200,
    result: {
        nick: "zhangdianpeng"
    }
} ctx.response.body = {
    code: 200,
    result: {
        nick: "zhangdianpeng"
    }
}console.log('ctx.method:', ctx.method);console.log('ctx.request.method:', ctx.request.method);

免费体验云安全(易盾)内容安全、验证码等服务

更多网易技术、产品、运营经验分享请点击

相关文章:
【推荐】 网易杭研易盾实习心得(4)
【推荐】 如何玩转基于风险的测试
【推荐】 AndroidTVOverscan

新一代web框架Koa源码学习的更多相关文章

  1. ABP 框架从源码学习——abp框架启动和结束(1)

       1.abp框架的启动是从Global.asax文件的Application_Start启动的,当然代表Global的application必须从AbpWebApplication继承: publ ...

  2. ABP 框架从源码学习——abp框架启动核心类AbpBootstrapper(2)

    在AbpBootstrapper中的两个至关重要的属性:IIocManager 和 IAbpModuleManager  public class AbpBootstrapper : IDisposa ...

  3. Spring源码学习资料

    未完待续.. github地址 https://github.com/spring-projects 学习地址 https://github.com/code4craft/tiny-spring 推荐 ...

  4. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

  5. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

  6. MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...

  7. [转]MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    本文转自:http://www.cnblogs.com/landeanfen/p/5989092.html 阅读目录 一.MVC原理解析 1.MVC原理 二.HttpHandler 1.HttpHan ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  9. Java并发包源码学习之AQS框架(四)AbstractQueuedSynchronizer源码分析

    经过前面几篇文章的铺垫,今天我们终于要看看AQS的庐山真面目了,建议第一次看AbstractQueuedSynchronizer 类源码的朋友可以先看下我前面几篇文章: <Java并发包源码学习 ...

随机推荐

  1. Qt之任务栏系统托盘图标

    转自  --> http://blog.csdn.net/qivan/article/details/7506306 托盘图标,一个自己脑子出现很久的词,可惜自己都没动手去实现.最近看见的,听见 ...

  2. jquery带下拉菜单和焦点图

    jQuery,下拉菜单,二级菜单,索引按钮,焦点图代码,jquery带下拉菜单和焦点图是一款顶部通栏带二级下拉菜单和banner导航菜单代码. JQuery特效代码来源:http://www.huiy ...

  3. Luogu P1463 [HAOI2007]反素数ant:数学 + dfs【反素数】

    题目链接:https://www.luogu.org/problemnew/show/P1463 题意: 对于任何正整数x,其约数的个数记作g(x).例如g(1)=1.g(6)=4. 如果某个正整数x ...

  4. BZOJ 3296 [USACO2011 Open] Learning Languages:并查集

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3296 题意: 农夫约翰的N(2 <= N <= 10,000)头奶牛,编号为1 ...

  5. 双系统重装win7和ubuntu修复win7引导方法介绍(来源百度经验)

    很多朋友喜欢为电脑安装win7和ubuntu双系统,当我们重装双系统时,可能会出现win7引导不见的情况,接下来就告诉大家双系统重装win7和ubuntu修复win7引导的方法. 1.win7和ubu ...

  6. 大数据_学习_01_Hadoop 2.x及hbase常用端口及查看方法

    二.参考资料 1.Hadoop 2.x常用端口及查看方法

  7. Tomcat启动分析(我们为什么要配置CATALINA_HOME环境变量)

    原文:http://www.cnblogs.com/heshan664754022/archive/2013/03/27/2984357.html Tomcat启动分析(我们为什么要配置CATALIN ...

  8. luogu1776宝物筛选

    多重背包问题 一开始我们的转移方程是 ;i<=n;i++) for(int j=m;j>=w[i];j--) ;k<=c[i];k++) )dp[j]=max(dp[j],dp[j- ...

  9. POJ3565 Ants 和 POJ2195 Going Home

    Ants Language:Default Ants Time Limit: 5000MS Memory Limit: 65536K Total Submissions: 7975 Accepted: ...

  10. Excel对重复数据分组,求出不同的数据(office 2013)

    第一步: 第二步: 第三步: