此文已由做者张佃鹏受权网易云社区发布。
node
欢迎访问网易云社区,了解更多网易技术产品运营经验。git
Koa 就是一种简单好用的 Web 框架。它的特色是优雅、简洁、表达力强、自由度高。自己代码只有1000多行。koa一个中间件框架,其提供的是一个架子,而几乎全部的功能都须要由第三方中间件完成,它只是node原生的http的一个封装,再加入中间件元素,koa 不在内核方法中绑定任何中间件, 它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得驾轻就熟github
Koa目前分为两个版本:koa 1.0和koa2数组
koa 1.0: 依赖generator函数和Promise实现异步处理(ES6)安全
koa2: 依赖async函数和Promise实现异步处理(ES7)cookie
如下的关于koa的介绍主要在koa2的基础上进行分析:app
koa框架主要由如下几个元素组成:框架
const Koa = require('koa');const app = new Koa();
app的主要属性以下:dom
proxy: 表示是否开启代理信任开关,默认为false,若是开启代理信任,对于获取request请求中的host,protocol,ip分别优先从Header字段中的X-Forwarded-Host,X-Forwarded-Proto,X-Forwarded-For获取:koa
//如下是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是中间件中的上下文环境,也是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