koa2 源码解读 application
koa2的源码比较简单,重点解读aplication,
其中context源码比较简单,主要是一些error cookies等,重点可以关注下delegate,delegate模块中,主要通过prototype的方式进行属性的增加。
request和response两个模块都是get set的一些基础api及封装的node原始方法
application
'use strict'; //严格模式 /**
* Module dependencies.
*/ const isGeneratorFunction = require('is-generator-function');
const debug = require('debug')('koa:application');
const onFinished = require('on-finished');
const response = require('./response');
const compose = require('koa-compose');
const isJSON = require('koa-is-json');
const context = require('./context');
const request = require('./request');
const statuses = require('statuses');
const Emitter = require('events');
const util = require('util');
const Stream = require('stream');
const http = require('http');
const only = require('only');
const convert = require('koa-convert');
const deprecate = require('depd')('koa');
const { HttpError } = require('http-errors'); /**
* constructor() 构造函数
* listen() 调用原生http模块创建服务并监听
* use() 中间件处理
* callback() http请求的回调函数
* handleRequest() 请求真正的回调函数
* createContext() 创建上下文对象
* respond() 所有中间件处理完后自动响应
* onerror() 处理错误信息
*
*/ /**
* Expose `Application` class.
* Inherits from `Emitter.prototype`.
*/ module.exports = class Application extends Emitter {
/**
* Initialize a new `Application`.
*
* @api public
*/ /**
*
* @param {object} [options] Application options
* @param {string} [options.env='development'] Environment
* @param {string[]} [options.keys] Signed cookie keys
* @param {boolean} [options.proxy] Trust proxy headers
* @param {number} [options.subdomainOffset] Subdomain offset
*
*/ constructor(options) {
super();
options = options || {};
this.proxy = options.proxy || false; //是否允许跨域
this.subdomainOffset = options.subdomainOffset || 2; // 子域名允许请求几级连接
this.env = options.env || process.env.NODE_ENV || 'development'; //node的执行环境
if (options.keys) this.keys = options.keys;
this.middleware = []; //所有的中间件的存入
this.context = Object.create(context); //每次实例化都重新赋值,为保证多次实例化时保持不冲突,和单例模式成反例
this.request = Object.create(request);
this.response = Object.create(response);
if (util.inspect.custom) {
this[util.inspect.custom] = this.inspect; //保存88行代码中的内容
}
} /**
* Shorthand for:
*
* http.createServer(app.callback()).listen(...)
*
* @param {Mixed} ...
* @return {Server}
* @api public
*/ listen(...args) {
debug('listen');
const server = http.createServer(this.callback()); //原生http模块创建服务并监听
return server.listen(...args);
} /**
* Return JSON representation.
* We only bother showing settings.
*
* @return {Object}
* @api public
*/ toJSON() {
return only(this, [ //only 对传入的数据使用reduce进行重组
'subdomainOffset',
'proxy',
'env'
]);
} /**
* Inspect implementation.
*
* @return {Object}
* @api public
*/ inspect() {
return this.toJSON(); //数据重组
} /**
* Use the given middleware `fn`.
*
* Old-style middleware will be converted.
*
* @param {Function} fn
* @return {Application} self
* @api public
*/ use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');//必须是一个function
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || '-'); //DEBUG=koa* node --harmony app.js 调试时输出中间件调用及时长
this.middleware.push(fn); //将中间件加入到middleware数组中
return this;
} /**
* Return a request handler callback
* for node's native http server.
*
* @return {Function}
* @api public
*/ callback() {
const fn = compose(this.middleware); //将这些中间件组合后拿到执行链函数fn if (!this.listenerCount('error')) this.on('error', this.onerror); //如果没有监听则报错 const handleRequest = (req, res) => { //事件处理函数
const ctx = this.createContext(req, res); //创建一个ctx
return this.handleRequest(ctx, fn); //交给157行的handleRequest
}; return handleRequest;
} /**
* Handle request in callback.
*
* @api private
*/ handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404; //初始赋值
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx); //211行详解
onFinished(res, onerror);
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
} /**
* Initialize a new context.
*
* @api private
*/ createContext(req, res) {
const context = Object.create(this.context);//通过context对象的原型创建
const request = context.request = Object.create(this.request);//通过request对象的原型创建,this.request指的是原生的request,修改this.request中的属性就是修改原生的对应的属性数据
const response = context.response = Object.create(this.response);//通过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;
} /**
* Default error handler.
*
* @param {Error} err
* @api private
*/ onerror(err) {
if (!(err instanceof Error)) throw new TypeError(util.format('non-error thrown: %j', err)); //检测err不是Error实例时,创建一个Error的实例 if (404 == err.status || err.expose) return;
if (this.silent) return; const msg = err.stack || err.toString(); //将err堆栈的信息拿出来
console.error(); //控制台打印Error信息
console.error(msg.replace(/^/gm, ' '));
console.error();
}
}; /**
* Response helper.
*/ function respond(ctx) {
// allow bypassing koa
if (false === ctx.respond) return; //允许绕过KOA 为写入原始的res对象而不是让koa处理你的rsponse if (!ctx.writable) return; const res = ctx.res;
let body = ctx.body; //外面Middleware传入的body数据
const code = ctx.status; //当前状态码 // ignore body
if (statuses.empty[code]) { //当前状态码是空的,则清掉body并结束
// strip headers
ctx.body = null;
return res.end();
} if ('HEAD' == ctx.method) { //head部分
if (!res.headersSent && isJSON(body)) { //判断当前header没有被发送并且是 重组后的json数据
ctx.length = Buffer.byteLength(JSON.stringify(body)); //则重新序列化 取长度
}
return res.end();
} // status body
if (null == body) { // body部分 不为null
if (ctx.req.httpVersionMajor >= 2) { //根据http major的版本 分别对body进行初始化
body = String(code);
} else {
body = ctx.message || String(code);
}
if (!res.headersSent) {
ctx.type = 'text';
ctx.length = Buffer.byteLength(body);
}
return res.end(body);
}
//以下为支持各种格式的body
// responses
if (Buffer.isBuffer(body)) return res.end(body);
if ('string' == typeof body) return res.end(body);
if (body instanceof Stream) return body.pipe(res); // body: json
body = JSON.stringify(body);
if (!res.headersSent) {
ctx.length = Buffer.byteLength(body);
}
res.end(body);
} /**
* Make HttpError available to consumers of the library so that consumers don't
* have a direct dependency upon `http-errors`
*/
module.exports.HttpError = HttpError;
koa2 源码解读 application的更多相关文章
- koa2源码解读及实现一个简单的koa2框架
阅读目录 一:封装node http server. 创建koa类构造函数. 二:构造request.response.及 context 对象. 三:中间件机制的实现. 四:错误捕获和错误处理. k ...
- koa2源码解读
最近在复习node的基础知识,于是看了看koa2的源码,写此文分享一下包括了Koa2的使用.中间件及上下文对象的大致实现原理. koa的github地址:https://github.com/koaj ...
- koa源码解读
koa是有express原班人马打造的基于node.js的下一代web开发框架.koa 1.0使用generator实现异步,相比于回调简单和优雅和不少.koa团队并没有止步于koa 1.0, 随着n ...
- SDWebImage源码解读之SDWebImageCache(下)
第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...
- AFNetworking 3.0 源码解读 总结(干货)(下)
承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...
- AFNetworking 3.0 源码解读 总结(干货)(上)
养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...
- AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager
让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...
- AFNetworking 3.0 源码解读(八)之 AFImageDownloader
AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...
- AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization
这篇就讲到了跟请求相关的类了 关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点. 上半篇: URI编码的知识 关于 ...
随机推荐
- String.indexOf()的使用方法
String.indexOf()的用途: 返回此字符串中第一个出现的指定的子字符串,如果没有找到则返回-1 源码如下: /** * Returns the index within this stri ...
- NameVirtualHost的使用
如果某个ip:port 上只设置了一个虚拟主机,那么NameVirtualHost ip:port 可以不写,因为从这个ip:port的访问不需要做任何选择:如上例中192.168.1.197只对应于 ...
- 进程通信(multiprocessing.Queue)
from multiprocessing import Queue Queue([maxsize]) 创建共享的进程队列.maxsize是队列中允许的最大项数.如果省略此参数,则无大小限制.底层队列使 ...
- Taro -- 原生微信小程序转taro
微信小程序转Taro (转发https://nervjs.github.io/taro/docs/taroize.html) Taro 可以将你的原生微信小程序应用转换为 Taro 代码,进而你可以 ...
- c++函数参数或返回值为函数指针
C++中函数指针的形式为:返回值类型 + 参数类型,函数没有值类型,但是却可以声明函数的指针,因为函数是可寻址的,存放在内存中的代码段,可以从指针访问. 函数指针可以声明为: void (*pF)(v ...
- ps:消除锯齿和羽化
我们使用椭圆选框工具,分别关闭和打开消除锯齿(也称抗锯齿),创建两个差不多大的正圆形选区,然后填充黑色看看.怎样快速填充黑色还记得吗?先[X]然后[ALT DELETE].填充之后取消选择[CTRL ...
- 如何搭建一个spring boot项目
什么是springboot? Spring Boot俗称微服务.Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特 ...
- Ubuntu下批量使用Tecplot的preplot命令对数据进行处理
首先把.PLT文件后缀批量修改为.dat rename 's/.PLT$/.dat/' *.PLT 对所有.dat文件批量执行preplot find ./ -name "*.dat&quo ...
- RMQ Terminology
原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/11784644.html RMQ模型架构 RMQ Terminology Message 消息,消息是不 ...
- DEDECMS编辑器粘贴Word
我司需要做一个需求,就是使用富文本编辑器时,不要以上传附件的形式上传图片,而是以复制粘贴的形式上传图片. 在网上找了一下,有一个插件支持这个功能. WordPaster 安装方式如下: 直接使用Wor ...