最近在复习node的基础知识,于是看了看koa2的源码,写此文分享一下包括了Koa2的使用、中间件及上下文对象的大致实现原理。

koa的github地址:https://github.com/koajs/koa.git

Koa2的安装和简单使用

需要 nodev7.6.0 或者更高的版本,为了支持 ES2015 and async

安装
npm install koa
Hello koa
const Koa = require('koa');
const app = new Koa(); // response
app.use(ctx => {
ctx.body = 'Hello Koa';
}); app.listen(3000);
中文的api文档:https://github.com/guo-yu/koa-guide

简单分析koa的代码

打开koa的源码,核心文件共四个在lib目录下,application.js,context.js,request.js,response.js

application.js

app的入口文件,就是一个构造函数

 简洁的代码
module.exports = class Application extends Emitter {
constructor() {
super();
//定义下面的属性
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);
}
//listen端口方法
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
} toJSON() {
return only(this, [
'subdomainOffset',
'proxy',
'env'
]);
} inspect() {
return this.toJSON();
} //中间件使用的use方法
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a 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 || '-');
this.middleware.push(fn);
return this;
} //上下文等关键代码
callback() {
const fn = compose(this.middleware); if (!this.listeners('error').length) this.on('error', this.onerror); const handleRequest = (req, res) => {
res.statusCode = 404;
const ctx = this.createContext(req, res);
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
return fn(ctx).then(handleResponse).catch(onerror);
}; return handleRequest;
} //创建上下文
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.cookies = new Cookies(req, res, {
keys: this.keys,
secure: request.secure
});
request.ip = request.ips[0] || req.socket.remoteAddress || '';
context.accept = request.accept = accepts(req);
context.state = {};
return context;
} //处理报错
onerror(err) {
assert(err instanceof Error, `non-error thrown: ${err}`); if (404 == err.status || err.expose) return;
if (this.silent) return; const msg = err.stack || err.toString();
console.error();
console.error(msg.replace(/^/gm, ' '));
console.error();
}
};

开始的流程:

const app = new Koa();

然后通过 listen来启动服务:

const server = http.createServer(this.callback());
server.listen(...args);

看一下原生的启动方法:

// http server 例子
var server = http.createServer(function(serverReq, serverRes){
var url = serverReq.url;
serverRes.end( '您访问的地址是:' + url );
});
server.listen(3000);

对比发现this.callback()就是用来创建上下文和处理req和res的,接着看this.callback那个方法:

//处理中间件的使用,后面详细说明
const fn = compose(this.middleware); if (!this.listeners('error').length) this.on('error', this.onerror); const handleRequest = (req, res) => {
res.statusCode = 404;
// 创建上下文
const ctx = this.createContext(req, res);
const onerror = err => ctx.onerror(err); const handleResponse = () => respond(ctx);
onFinished(res, onerror);
//中间件返回promise对象,成功执行handleResponese,错误用onerror处理,
return fn(ctx).then(handleResponse).catch(onerror);
};
返回callback函数
return handleRequest;

启动服务:

server.listen(...args);

到此服务就起来了。在来看看中间件的使用原理:

use(fn) {
//判断做兼容处理
if (typeof fn !== 'function') throw new TypeError('middleware must be a 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 || '-');
//使用把中间件推送到middleware中保存
this.middleware.push(fn);
//返回this,为了连续调用
return this;
}

保存到this.middleware,在this.callback进程了处理:

const fn = compose(this.middleware);

看一下compose是怎么处理middleware,代码在const compose = require('koa-compose');

'use strict'

module.exports = compose

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!')
} return function (context, next) {
// last called middleware #
let index = -1
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 {
return Promise.resolve(fn(context, function next () {
//递归调用,直到全部中间件执行完
return dispatch(i + 1)
}))
} catch (err) {
//有错误
return Promise.reject(err)
}
}
}
}

通过上面巧妙的递归调用,执行完所有的中间件函数,返回继续启动流程,创建上下文,处理res,req等。

koa2源码解读的更多相关文章

  1. koa2源码解读及实现一个简单的koa2框架

    阅读目录 一:封装node http server. 创建koa类构造函数. 二:构造request.response.及 context 对象. 三:中间件机制的实现. 四:错误捕获和错误处理. k ...

  2. koa2 源码解读 application

    koa2的源码比较简单,重点解读aplication, 其中context源码比较简单,主要是一些error cookies等,重点可以关注下delegate,delegate模块中,主要通过prot ...

  3. koa源码解读

    koa是有express原班人马打造的基于node.js的下一代web开发框架.koa 1.0使用generator实现异步,相比于回调简单和优雅和不少.koa团队并没有止步于koa 1.0, 随着n ...

  4. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  5. SDWebImage源码解读 之 NSData+ImageContentType

    第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...

  6. SDWebImage源码解读 之 UIImage+GIF

    第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...

  7. SDWebImage源码解读 之 SDWebImageCompat

    第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...

  8. SDWebImage源码解读_之SDWebImageDecoder

    第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...

  9. SDWebImage源码解读之SDWebImageCache(上)

    第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...

随机推荐

  1. 解决Layui的switch样式显示问题

    Layui官方文档是这么说的: <input type="checkbox" name="xxx" lay-skin="switch" ...

  2. MovieReview—Kingsman THE SECRET SERVICE(王牌特工之特工学院)

    Brain Storming         Mr. Valentine's Day see excess human beings as the Earth's virus and try to e ...

  3. 2018.2.25 关于JavaScript

    关于JavaScript 1.数组的归约函数reduce(function(PREV,CUR,I){})会从做导游进行迭代,每次返回的值为下一次的prev参数. 2.在循环遍历数组时若是想在找到结果后 ...

  4. [BZOJ4327]:[JZOI2012]玄武密码(AC自动机)

    题目传送门 题目描述: 在美丽的玄武湖畔,鸡鸣寺边,鸡笼山前,有一块富饶而秀美的土地,人们唤作进香河.相传一日,一缕紫气从天而至,只一瞬间便消失在了进香河中.老人们说,这是玄武神灵将天书藏匿在此.  ...

  5. Google 出品的 Java 编码规范,强烈推荐,权威又科学!

    原文:google.github.io/styleguide/javaguide.html 译者:Hawstein 来源:hawstein.com/2014/01/20/google-java-sty ...

  6. Bootstrap历练实例:导航内的下拉菜单

    <!DOCTYPE html><html><head><meta http-equiv="Content-Type" content=&q ...

  7. Java多线程 编写三各类Ticket、SaleWindow、TicketSaleCenter分别代表票信息、售票窗口、售票中心。 售票中心分配一定数量的票,由若干个售票窗口进行出售,利用你所学的线程知识来模拟此售票过程。

    package com.swift; import java.util.ArrayList; import java.util.HashMap; import java.util.List; impo ...

  8. 简单的cocos2dx笔试题

    1.参数传递有几种方式?值传递.指针传递.引用传递 2.指针和引用有什么分别:如果传引用比传指针安全,为什么?如果我使用常量指针难道不行吗? 1.指针是一个变量,存储一个地址,指向内存的一个存储单元: ...

  9. python queue - 同步队列类

    参考 官网 queue 模块 queue 模块实现多生产者,多消费者队列. 当必须在 ==多个线程之间安全地交换信息== 时,它在线程编程中特别有用. 此模块中的Queue类实现了所有必需的锁定语义. ...

  10. BZOJ-3679(数位DP)

    #include <bits/stdc++.h> using namespace std; typedef long long ll; ll a,b; int k[20]; ll dp[2 ...