wangjiong's blog

everyday is new day


  • 首页

  • 归档

  • 分类

  • 标签

一步一步实现一个小的koa

发表于 2017-06-08 | 分类于 nodejs

koa对象

1 http服务创建
2 中间件的添加
3 为中间件传入请求,响应参数
4 中间件的执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
const Emitter = require('events');
const http = require('http');
module.exports = class Application extends Emitter {
constructor() {
super();
this.middleware = [];
}
// 创建http服务,监听端口
listen() {
const server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
}
// 接收到http请求,触发的回调函数
callback() {
console.log('http callback')
console.log(this)
const fn = this.compose(this.middleware);
const handleRequest = (request, response) => {
// 把http请求,响应放进context传入中间件
const context = Object.create(null, {
request: {value: request},
response: {value: response},
})
return fn(context).catch((err) => {
console.log('err')
console.log(err)
});
};
return handleRequest;
}
// 添加中间件
use(fn) {
this.middleware.push(fn);
return this;
}
// 执行所有中间件
compose(middleware) {
console.log('compose')
console.log(middleware)
return function(context, next) {
console.log('dispatch')
let index = -1
return dispatch(0)
function dispatch(i) {
console.log('dispatch:' + i)
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)
}
}
}
}
}

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const Koa = require('./application')
const app = new Koa()
// x-response-time
app.use(async function (context, next) {
console.log('x-response-time start')
console.log(context)
const start = new Date()
await next()
const ms = new Date() - start
console.log('x-response-time end')
})
// logger
app.use(async function (context, next) {
console.log('logger start')
const start = new Date()
await next()
const ms = new Date() - start
console.log('logger end')
})
// response
app.use(context => {
// 可以使用原生的方法处理请求,响应
console.log('hello world')
context.response.stateCode = '200'
context.response.end('Hello World')
})
app.listen(3001)

最终输出log

koa-dispatch 中间件执行发起

机器学习-朴素贝叶斯

发表于 2017-06-06 | 分类于 机器学习

贝叶斯

贝叶斯推断:先估计一个值,根据实际结果不断修正。

贝叶斯定理:P(A|B),条件概率,事件B发生的情况下,事件A发生的概率。

文氏图
由文氏图得
P(A∣B)=P(A⋂B)P(B) P \left ( A|B \right ) = \frac {P \left ( A \bigcap B \right )} {P \left ( B \right )} P(A∣B)=​P(B)​​P(A⋂B)​​
又
P(B∣A)=P(A⋂B)P(B) P \left ( B|A \right ) = \frac {P \left ( A \bigcap B \right )} {P \left ( B \right )} P(B∣A)=​P(B)​​P(A⋂B)​​
故
P(A⋂B)=P(A∣B)∗P(B)=P(B∣A)∗P(A) P \left ( A \bigcap B \right ) = P \left ( A|B \right ) \ast P \left ( B \right ) = P \left ( B|A \right ) \ast P \left ( A \right ) P(A⋂B)=P(A∣B)∗P(B)=P(B∣A)∗P(A)
最终
P(A∣B)=P(B∣A)∗P(A)P(B) P \left ( A|B \right ) = \frac{P \left ( B|A \right ) \ast P \left ( A \right )}{P \left ( B \right )} P(A∣B)=​P(B)​​P(B∣A)∗P(A)​​

全概率公式

由文氏图得
P(B)=P(B⋂A)+P(B⋂A′) P \left ( B \right ) = P \left ( B \bigcap A \right ) + P \left ( B \bigcap{A}' \right ) P(B)=P(B⋂A)+P(B⋂A​′​​)
由条件概率公式得
P(B⋂A)=P(B∣A)∗P(A) P \left ( B \bigcap A \right ) = P \left ( B|A \right ) \ast P \left ( A \right ) P(B⋂A)=P(B∣A)∗P(A)
代入得全概率公式
P(B)=P(B∣A)∗P(A)+P(B∣A′)∗P(A′) P \left ( B \right ) = P \left ( B|A \right ) \ast P \left ( A \right ) + P \left ( B|{A}' \right ) \ast P \left ( {A}' \right ) P(B)=P(B∣A)∗P(A)+P(B∣A​′​​)∗P(A​′​​)
代入条件概率公式得到条件概率另一种写法

P(A∣B)=P(B∣A)∗P(A)P(B)=P(B∣A)∗P(A)P(B∣A)∗P(A)+P(B∣A′)∗P(A′) P \left ( A|B \right ) = \frac{P \left ( B|A \right ) \ast P \left ( A \right )}{P \left ( B \right )} = \frac{P\left ( B|A \right )\ast P\left ( A \right )}{P \left ( B|A \right ) \ast P \left ( A \right ) + P \left ( B|{A}' \right ) \ast P \left ( {A}' \right )} P(A∣B)=​P(B)​​P(B∣A)∗P(A)​​=​P(B∣A)∗P(A)+P(B∣A​′​​)∗P(A​′​​)​​P(B∣A)∗P(A)​​

贝叶斯推断

条件概率
P(A∣B)=P(A)∗P(B∣A)P(B) P \left ( A|B \right ) = P \left ( A \right ) \ast \frac{P \left ( B|A \right )}{P \left ( B \right )} P(A∣B)=P(A)∗​P(B)​​P(B∣A)​​
P(A):先验概率,在事件B发生之前,事件A发生的概率,与事件B无关。
P(A|B):后验概率,在事件B发生之后,事件A发生的概率。
P(B|A)/P(B):可能性函数,用于调整先验概率,使之接近后验概率。若大于1,先验概率被增强,后验概率变大,反之,先验概率被削弱,后验概率变小。

使用贝叶斯过滤垃圾邮件

假定先验概率,垃圾邮件的概率P(S)=50%,正常邮件的概率P(H)=50%
根据条件概率公式,某个词语存在的条件下,垃圾邮件的概率:
P(S∣W)=P(W∣S)∗P(S)P(W∣S)∗P(S)+P(W∣H)∗P(H) P \left ( S|W \right ) = \frac{P\left ( W|S \right ) \ast P\left ( S \right )}{P\left ( W|S \right )\ast P\left ( S \right )+P\left ( W|H \right )\ast P\left ( H \right )} P(S∣W)=​P(W∣S)∗P(S)+P(W∣H)∗P(H)​​P(W∣S)∗P(S)​​
需要求出P(W|S)垃圾邮件中W单词的概率和P(W|H)正常邮件中W单词的概率
一般情况下我们需要根据多个单词的出现判断垃圾邮件
P1 = P(S|W1)
P2 = P(S|W2)

事件 单词W1 单词W2 是否是垃圾邮件
垃圾邮件E1 P1 P2 P(S)
正常邮件E2 1-P1 1-P2 1-P(S)

假设事件独立
P(E1) = P(S|W1)P(S|W2)P(S)
P(E2) = (1-P(S|W1))(1-P(S|W2))(1-P(S))
P=P(E1)P(E1)+P(E2)=P(S∣W1)P(S∣W2)P(S)P(S∣W1)P(S∣W2)P(S)+(1−P(S∣W1)(1−P(S∣W2)(1−P(S))))P=\frac{P\left ( E1 \right )}{P\left ( E1 \right ) + P\left ( E2 \right )}=\frac{P(S|W1)P(S|W2)P(S)}{P(S|W1)P(S|W2)P(S)+(1-P(S|W1)(1-P(S|W2)(1-P(S))))}P=​P(E1)+P(E2)​​P(E1)​​=​P(S∣W1)P(S∣W2)P(S)+(1−P(S∣W1)(1−P(S∣W2)(1−P(S))))​​P(S∣W1)P(S∣W2)P(S)​​
代入P(S)=0.5
P=P(S∣W1)P(S∣W2)P(S∣W1)P(S∣W2)+(1−P(S∣W1)(1−P(S∣W2)))P=\frac{P(S|W1)P(S|W2)}{P(S|W1)P(S|W2)+(1-P(S|W1)(1-P(S|W2)))}P=​P(S∣W1)P(S∣W2)+(1−P(S∣W1)(1−P(S∣W2)))​​P(S∣W1)P(S∣W2)​​
P=P1P2P1P2+(1−P1)(1−P2)P=\frac{P1P2}{P1P2 + (1-P1)(1-P2)}P=​P1P2+(1−P1)(1−P2)​​P1P2​​
扩展到所有单词
P=P1P2...PnP1P2...Pn+(1−P1)(1−P2)...(1−Pn)P=\frac{P1P2...Pn}{P1P2...Pn + (1-P1)(1-P2)...(1-Pn)}P=​P1P2...Pn+(1−P1)(1−P2)...(1−Pn)​​P1P2...Pn​​

朴素贝叶斯三个模型(scikit-learn)

高斯Gaussian:特征值分布符合高斯分布
多项式Multinomial:适合文本分类
伯努利Bernoulli:特征取值是布尔型的

人工智能应用收集

发表于 2017-06-06 | 分类于 机器学习
名称 介绍 链接
logojoy 使用遗传算法生成logo https://www.logojoy.com/
PaintsChainer 漫画线稿自动上色 https://github.com/pfnet/PaintsChainer
arkie 生成设计海报 http://www.arkie.cn/

koa-router源码分析

发表于 2017-05-31 | 分类于 nodejs

本文koa-router版本是7.2.0

路由定义:根据请求url路径,通过判断或正则匹配返回对应的页面。

示例:

原生示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const Koa = require('koa')
const app = new Koa()
async function route( url ) {
let view = `<html><header></header><body>404</body></html>`
switch ( url ) {
case '/':
view = `<html><header></header><body>index</body></html>`
break
case '/index':
view = `<html><header></header><body>index</body></html>`
break
case '/todo':
view = `<html><header></header><body>to do</body></html>`
break
case '/404':
view = `<html><header></header><body>404</body></html>`
break
default:
break
}
let html = view
return html
}
app.use( async ( ctx ) => {
let url = ctx.request.url
let html = await route( url )
ctx.body = html
})
app.listen(3000)

一个简单的koa-router例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
var router = new Router()
router.get('/', function (ctx, next) {
console.log(ctx.router)
console.log(ctx.params)
let html = `
<p>/</p>
`
ctx.body = html
})
router.get('/name/:id', function (ctx, next) {
let html = `
<p>name:${ctx.params.id}</p>
`
ctx.body = html
})
router.get('/company', function (ctx, next) {
let html = `
<p>company</p>
`
ctx.body = html
})
app.use(router.routes()).use(router.allowedMethods())
console.log(router)
app.listen(3000)

多个子router的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
// 子路由1
let home = new Router()
home.get('/', async ( ctx )=>{
let html = `
<ul>
<li><a href="/page/helloworld">/page/helloworld</a></li>
<li><a href="/page/404">/page/404</a></li>
</ul>
`
ctx.body = html
})
// 子路由2
let page = new Router()
page.get('/404', async ( ctx )=>{
ctx.body = '404 page!'
}).get('/helloworld', async ( ctx )=>{
ctx.body = 'helloworld page!'
})
// 装载子路由
let router = new Router()
router.use('/', home.routes(), home.allowedMethods())
router.use('/page', page.routes(), page.allowedMethods())
// 加载路由中间件
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)

路由嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
let home = new Router()
let page = new Router()
page.get('/:pageid', async ( ctx )=>{
let html = `
<p>page:${ctx.params.pageid}</p>
`
ctx.body = html
})
// /home/232/page
home.use('/home/:fid/page', page.routes(), page.allowedMethods());
// 加载路由中间件
app.use(home.routes())
app.listen(3000)

路由前缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
let router = new Router({
prefix: '/users'
})
// /users
router.get('/:pageid', async ( ctx )=>{
let html = `
<p>user home</p>
`
ctx.body = html
})
// /users/:userid
router.get('/:userid', async ( ctx )=>{
let html = `
<p>user-id:${ctx.params.userid}</p>
`
ctx.body = html
})
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)

Router的结构

Router构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module.exports = Router;
function Router(opts) {
if (!(this instanceof Router)) {
return new Router(opts);
}
this.opts = opts || {};
this.methods = this.opts.methods || [
'HEAD',
'OPTIONS',
'GET',
'PUT',
'PATCH',
'POST',
'DELETE'
];
this.params = {};
//stack存储不同的Layer,Router和Layer的关系是Router包含Layer
this.stack = [];
};

Layer构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function Layer(path, methods, middleware, opts) {
this.opts = opts || {};
this.name = this.opts.name || null;
this.methods = [];
this.paramNames = [];
this.stack = Array.isArray(middleware) ? middleware : [middleware];
methods.forEach(function(method) {
var l = this.methods.push(method.toUpperCase());
if (this.methods[l-1] === 'GET') {
this.methods.unshift('HEAD');
}
}, this);
// ensure middleware is a function
this.stack.forEach(function(fn) {
var type = (typeof fn);
if (type !== 'function') {
throw new Error(
methods.toString() + " `" + (this.opts.name || path) +"`: `middleware` "
+ "must be a function, not `" + type + "`"
);
}
}, this);
this.path = path;
this.regexp = pathToRegExp(path, this.paramNames, this.opts);
debug('defined route %s %s', this.methods, this.opts.prefix + this.path);
};

Router对象

可以看出layer存储匹配规则等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Router {
opts: {},
methods: [ 'HEAD', 'OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE' ],
params: {},
stack:
[ Layer {
opts: [Object],
name: null,
methods: [Object],
paramNames: [],
stack: [Object],
path: '/',
regexp: /^(?:\/(?=$))?$/i },
Layer {
opts: [Object],
name: null,
methods: [Object],
paramNames: [Object],
stack: [Object],
path: '/name/:id',
regexp: /^\/name\/((?:[^\/]+?))(?:\/(?=$))?$/i },
Layer {
opts: [Object],
name: null,
methods: [Object],
paramNames: [],
stack: [Object],
path: '/company',
regexp: /^\/company(?:\/(?=$))?$/i } ] }

path的匹配

分两层,Router遍历所有layer,返回匹配的matched对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Router.prototype.match = function (path, method) {
var layers = this.stack;
var layer;
var matched = {
//存储path匹配的layer
path: [],
//存储methods匹配的layer
pathAndMethod: [],
// 是否匹配成功
route: false
};
for (var len = layers.length, i = 0; i < len; i++) {
layer = layers[i];
debug('test %s %s', layer.path, layer.regexp);
if (layer.match(path)) {
matched.path.push(layer);
if (layer.methods.length === 0 || ~layer.methods.indexOf(method)) {
matched.pathAndMethod.push(layer);
if (layer.methods.length) matched.route = true;
}
}
}
return matched;
};

Layer层通过正则匹配路径

1
2
3
Layer.prototype.match = function (path) {
return this.regexp.test(path);
};

Router.use()

Router通过use()将methods方法与Router联系起来app.use(router.routes()).use(router.allowedMethods());
router.routes()返回一个中间件,用于对请求发起路由匹配,把一些router参数加入ctx对象,执行router.routes(),返回的是一个dispatch(ctx, next)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Router.prototype.routes = Router.prototype.middleware = function () {
var router = this;
var dispatch = function dispatch(ctx, next) {
debug('%s %s', ctx.method, ctx.path);
// 获取path
var path = router.opts.routerPath || ctx.routerPath || ctx.path;
// 发起path match,获取matched对象
var matched = router.match(path, ctx.method);
var layerChain, layer, i;
if (ctx.matched) {
ctx.matched.push.apply(ctx.matched, matched.path);
} else {
ctx.matched = matched.path;
}
// 可以从ctx 取router
ctx.router = router;
// 判断是否匹配成功
if (!matched.route) return next();
var matchedLayers = matched.pathAndMethod
var mostSpecificLayer = matchedLayers[matchedLayers.length - 1]
ctx._matchedRoute = mostSpecificLayer.path;
if (mostSpecificLayer.name) {
ctx._matchedRouteName = mostSpecificLayer.name;
}
layerChain = matchedLayers.reduce(function(memo, layer) {
memo.push(function(ctx, next) {
ctx.captures = layer.captures(path, ctx.captures);
ctx.params = layer.params(path, ctx.captures, ctx.params);
return next();
});
return memo.concat(layer.stack);
}, []);
return compose(layerChain)(ctx, next);
};
dispatch.router = this;
return dispatch;
};

Router.allowedMethods()

执行router.allowedMethods(),返回allowedMethods(ctx, next)方法,判断请求的method是否被允许

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Router.prototype.allowedMethods = function (options) {
options = options || {};
var implemented = this.methods;
return function allowedMethods(ctx, next) {
return next().then(function() {
var allowed = {};
if (!ctx.status || ctx.status === 404) {
ctx.matched.forEach(function (route) {
route.methods.forEach(function (method) {
allowed[method] = method;
});
});
var allowedArr = Object.keys(allowed);
// 判断请求method是否在允许范围内
if (!~implemented.indexOf(ctx.method)) {
if (options.throw) {
var notImplementedThrowable;
if (typeof options.notImplemented === 'function') {
notImplementedThrowable = options.notImplemented(); // set whatever the user returns from their function
} else {
notImplementedThrowable = new HttpError.NotImplemented();
}
throw notImplementedThrowable;
} else {
ctx.status = 501;
ctx.set('Allow', allowedArr);
}
} else if (allowedArr.length) {
if (ctx.method === 'OPTIONS') {
ctx.status = 204;
ctx.set('Allow', allowedArr);
} else if (!allowed[ctx.method]) {
if (options.throw) {
var notAllowedThrowable;
if (typeof options.methodNotAllowed === 'function') {
notAllowedThrowable = options.methodNotAllowed(); // set whatever the user returns from their function
} else {
notAllowedThrowable = new HttpError.MethodNotAllowed();
}
throw notAllowedThrowable;
} else {
ctx.status = 405;
ctx.set('Allow', allowedArr);
}
}
}
}
});
};
};

app.use(router.routes()).use(router.allowedMethods());

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
Router.prototype.use = function () {
var router = this;
// 将传入参数转换为数组
var middleware = Array.prototype.slice.call(arguments);
var path = '(.*)';
// support array of paths
if (Array.isArray(middleware[0]) && typeof middleware[0][0] === 'string') {
middleware[0].forEach(function (p) {
router.use.apply(router, [p].concat(middleware.slice(1)));
});
return this;
}
var hasPath = typeof middleware[0] === 'string';
if (hasPath) {
path = middleware.shift();
}
middleware.forEach(function (m) {
if (m.router) {
// 对router.routes()参数的处理
m.router.stack.forEach(function (nestedLayer) {
if (path) nestedLayer.setPrefix(path);
// 绑定layer
if (router.opts.prefix) nestedLayer.setPrefix(router.opts.prefix);
router.stack.push(nestedLayer);
});
if (router.params) {
Object.keys(router.params).forEach(function (key) {
m.router.param(key, router.params[key]);
});
}
} else {
// 创建并注册一个route
router.register(path, [], m, { end: false, ignoreCaptures: !hasPath });
}
});
return this;
};
// 创建并注册一个route
Router.prototype.register = function (path, methods, middleware, opts) {
opts = opts || {};
var router = this;
var stack = this.stack;
// support array of paths
if (Array.isArray(path)) {
path.forEach(function (p) {
router.register.call(router, p, methods, middleware, opts);
});
return this;
}
// create route Layer
var route = new Layer(path, methods, middleware, {
end: opts.end === false ? opts.end : true,
name: opts.name,
sensitive: opts.sensitive || this.opts.sensitive || false,
strict: opts.strict || this.opts.strict || false,
prefix: opts.prefix || this.opts.prefix || "",
ignoreCaptures: opts.ignoreCaptures
});
if (this.opts.prefix) {
route.setPrefix(this.opts.prefix);
}
// add parameter middleware
Object.keys(this.params).forEach(function (param) {
route.param(param, this.params[param]);
}, this);
stack.push(route);
return route;
};

koa源码分析

发表于 2017-05-25 | 分类于 nodejs

本文koa版本是2.2.0

创建koa服务:

  1. 创建koa的app对象
  2. 为app添加中间件
  3. 监听端口,创建server

下面是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const Koa = require('koa');
const app = new Koa();
// x-response-time
app.use(async function (ctx, next) {
console.log('x-response-time start')
const start = new Date();
await next();
const ms = new Date() - start;
ctx.set('X-Response-Time', `${ms}ms`);
console.log('x-response-time end')
});
// logger
app.use(async function (ctx, next) {
console.log('logger start')
const start = new Date();
await next();
const ms = new Date() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}`);
console.log('logger end')
});
// response
app.use(ctx => {
console.log('hello world')
ctx.body = 'Hello World';
});
app.listen(3000);

当请求http://localhost:3000/时,页面返回'Hello World’

中间件执行顺序

命令行里面顺序打印日志:’x-response-time start’ –> ‘logger start’ –> ‘hello world’ –> ‘logger end’ –> ‘x-response-time end’

async异步函数

从请求到响应类似下图

分析代码

app对象结构


创建Koa的app对象,Application继承Emitter对象,代码结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}
}

添加中间件

koa的中间件是很重要,使用app.use()添加中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use(fn) {
//判断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 || '-');
//把中间件函数push进application的middleware数组内
this.middleware.push(fn);
return this;
}

创建http服务

app.listen()监听端口,listen是createServer()的封装

1
2
3
4
5
listen() {
debug('listen');
const server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
}

接收到请求时的回调函数

当服务接收到http请求时,触发callback函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
// fn()
return fn(ctx).then(handleResponse).catch(onerror);
};
return handleRequest;
}

中间件

compose返回一个用于执行中间件的函数,在callback()函数执行fn(ctx),从dispatch(0)开始,执行第一个中间件函数,然后递归执行dispatch(i),执行中间件函数,直到执行完所有中间件函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 #
// 闭包,存储index变量,中间件执行当前坐标
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)
}
}
}
}

context保存请求,响应对象

context上下文用于管理请求,响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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,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;
}

请求和响应委托(Delegator)给context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// context 对象
const delegate = require('delegates');
const proto = module.exports = {
// 请求委托
delegate(proto, 'response')
.method('set')
.access('body')
.getter('headerSent')
// 响应委托
delegate(proto, 'request')
.method('get')
.access('url')
.getter('origin')
}
// delegate 实现
// proto 被委托的对象
function Delegator(proto, target) {
if (!(this instanceof Delegator)) return new Delegator(proto, target);
this.proto = proto;
this.target = target;
this.methods = [];
this.getters = [];
this.setters = [];
this.fluents = [];
}
// 获取委托对象
Delegator.prototype.access = function(name){
return this.getter(name).setter(name);
};
Delegator.prototype.getter = function(name){
var proto = this.proto;
var target = this.target;
this.getters.push(name);
proto.__defineGetter__(name, function(){
return this[target][name];
});
// 用于链式调用
return this;
};
Delegator.prototype.method = function(name){
var proto = this.proto;
var target = this.target;
this.methods.push(name);
proto[name] = function(){
// 方法委托
return this[target][name].apply(this[target], arguments);
};
return this;
};

response body 处理

分三种情况string,buffer,stream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
{
get body() {
return this._body;
},
set body(val) {
const original = this._body;
this._body = val;
if (this.res.headersSent) return;
// no content
if (null == val) {
if (!statuses.empty[this.status]) this.status = 204;
this.remove('Content-Type');
this.remove('Content-Length');
this.remove('Transfer-Encoding');
return;
}
// set the status
if (!this._explicitStatus) this.status = 200;
// set the content-type only if not yet set
const setType = !this.header['content-type'];
// string字符串处理
if ('string' == typeof val) {
if (setType) this.type = /^\s*</.test(val) ? 'html' : 'text';
this.length = Buffer.byteLength(val);
return;
}
// buffer
if (Buffer.isBuffer(val)) {
if (setType) this.type = 'bin';
this.length = val.length;
return;
}
// stream流
if ('function' == typeof val.pipe) {
onFinish(this.res, destroy.bind(null, val));
ensureErrorHandler(val, err => this.ctx.onerror(err));
// overwriting
if (null != original && original != val) this.remove('Content-Length');
if (setType) this.type = 'bin';
return;
}
// json
this.remove('Content-Length');
this.type = 'json';
},
}

123
Wang Jiong

Wang Jiong

happy coding

25 日志
4 分类
10 标签
© 2018 Wang Jiong
由 Hexo 强力驱动
主题 - NexT.Muse