Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

koa #19

Open
magicdawn opened this issue Jan 13, 2015 · 14 comments
Open

koa #19

magicdawn opened this issue Jan 13, 2015 · 14 comments

Comments

@magicdawn
Copy link
Owner

application.js 库

on-finished

require('on-finished')

判断res OutgoingMessage , req IncomingMessage, 的 状态(通过属性,socket状态)

  • 如果已经结束,立即调用 listener
  • 如果没有结束,用过ee-first , 给 finish,end,error 任意事件绑定listener
@magicdawn
Copy link
Owner Author

装一下 github-linker 就可以直接点进去

@magicdawn
Copy link
Owner Author

koa-compose 和 koa middleware 流程分析

var compose = require('koa-compose');

使用

var gen = compose(middlewares) => 返回 generator function

compose 代码

function compose(middleware){
  return function *(next){
    if (!next) next = noop();

    var i = middleware.length;

    while (i--) {
      next = middleware[i].call(this, next);
    }

    yield *next;
  }
}

后用 fn = co.wrap(gen),在app.callback里调用fn

co(function*(next){
    // 直接一个next不好理解,可以看 2.3.0 tag
    // https://github.com/koajs/compose/blob/2.3.0/index.js#L25

    var i = middleware.length;
    var prev = next || noop();
    var curr;

    while (i--) {
      curr = middleware[i];
      prev = curr.call(this, prev);
    }

    yield *next;
})

就是 从后往前把各个 middleware调用一些,由于是 GeneratorFunction,不会立即执行,而是返回generator,放在prev参数中传入前面一个,作为next...最后的 prev 就是 koa里面最前面的Respond middleware的 gen;

例如代码

var koa = require('koa');
var app = koa();

app.use(middleware1);

function* middleware1(next) {
    this.echo("step1");
    yield next;
    this.echo('step4');
}

app.use(middleware2);

function* middleware2(next) {
    this.echo('step2');
    yield next;
    this.echo('step3')
}

app.context.echo = function(text) {
    if (typeof this.body === 'undefined') {
        this.body = '';
    }

    this.body += text + '\n';
}

app.listen(3000, function() {
    console.log("server listening at http://localhost:3000");
});

这个输出顺序是 1234
流程是

  1. 在listen的时候,compose把各个middleware = [Respond,middleware1,middleware2],从后往前调用,获取generator,并传入前一个GeneratorFunction,意思是 在middleware1里面的next是middleware2的generator
  2. 在compose最后一句, yield *prev, 就是 yield respond的generator
  3. 在 co里面, toPromise(respond.gen).then(主Promise fulfill)
    主Promise是 上面 co(function(){ ... yield *prev })那个创建的Promise
    这个toPromise(gen) =>co(xxx). 等middleware2执行完,middleware1才fulfill,然后respond fulfill,然后结束

@magicdawn
Copy link
Owner Author

koa 成熟 ? 真心不懂 middleware之间跳来跳去有神马意思... 看了 koa-send / koa-static 很不方便的样子 , route也不方便

@magicdawn
Copy link
Owner Author

yield* https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield*

在koa库中大量用到 ...

可以yield* 另一个generator , 或 可 iterable 值,如 Array/String ...

在koa-* 库中,使用 yield* next , 比yield next 的区别是

  • yield next 会经过co封装next ,然后执行next
  • yield* next , 则是直接执行的next,没经过co , 说是更有效率一点... 但是MDN warn 该特性处于 ECMAScript 6 规范草案中,目前的实现在未来可能会发生微调,请谨慎使用。

yield* 值

值可以是任何值, 带输出值的, 例如 string, array, object
或者一个 generator , 但是 yield* GeneratorFunction 就出错了,
注意 yield* generator = yield* GenFn()

@magicdawn
Copy link
Owner Author

magicdawn commented Apr 28, 2015

koa 错误处理

  • context.onerror
  • application.onerror
  1. 所有的middleware throw的error
  2. catch 到 context 的 onerror
    1. emit app error, 如果在 app.callback() 之前没有绑定 app.on('error'), 默认的 app.onerror 就会使用
      这个 app.onerror , 非 err.expose 或者 NODE_ENV=test 的情况下打印 err.stack || err.toString()
    2. context.on error 做一些判断, 当 err.expose true时, 输出 err.message , 否则输出 status reason

context.throw

context.throw(status,message,props)

其实就是 throw 一个 ServerInternalError 实例, 并且有 status 属性,然后 有err.expose 属性,表示这个错误信息可以被发送到client端. 见 https://github.com/koajs/koa/blob/master/lib%2Fcontext.js#L99

use

  • 开发的时候, 打印控制台 & 输出网页
  • production, 出错页面
if( env != production ){
    app.on('error',app.onerror); // 调用默认的 console.error(error);
    app.on('error',function(err,ctx){
        err.expose = true; // 可expose, 但是 ctx.onerror 的输出只是 err.message
    });
} else {
    app.on('error',function(err,ctx){
        // 自己输出自定义页面 或者使用默认的 ctx.onerror 输出一段默认的原因
        // 如果是未找到文件, 输出404
        // ENOENT support
        if ('ENOENT' == err.code) err.status = 404;
    });
}

koa-onerror

const onerror = require('koa-onerror')

const app = new Koa
onerror(app)

patch 的是 app.context.onerror, 比默认的 app.context.onerror, 和原函数一样, 也会有 app.emit('error'), 比原函数多了模板输出

@magicdawn
Copy link
Owner Author

koa-router

var router = require('koa-router')();
...
app.use(router.routes());
app.use(router.allowedMethods());
...

这个换tj来写,肯定是 像express那种

var proto = {};
function Router(){
    var router = function * (){
        // dispatch here ...
    }
    router.__proto__ = proto;    
    return router;
}

proto.get / post / put / delete blabla ...

这样可以app.use(router) 就够了, koa-router 里的 router.routes 其实也就是 dispatch request

@magicdawn
Copy link
Owner Author

koa-send 发送文件

send(context,path,{
    root: <root>
}) 
// 简单的 createReadStream

koa-static 静态中间件

var serve = require('koa-static');
serve(root,options) -> send(context,context.path, {
    root: root
})

@magicdawn
Copy link
Owner Author

koa 中 context
req/res 是node 原生的,ctx.request ctx.response 是 koa 的request response

request 中关于path的部分

  • request.url
  • request.originalUrl
  • request.href
  • request.path
  • request.querystring
  • request.search
  • request.hostname
  • request.host

累觉不爱了!
先是 从req.url 保存 为originalUrl https://github.com/koajs/koa/blob/master/lib%2Fapplication.js#L149
然后 url是path+querystring ,href是完整的
其他的去http://koajs.com/#request 看吧

@magicdawn
Copy link
Owner Author

path-to-regexp

end的意思其实是, 这个regexp匹配完整路径么
image

end = false 的时候, 多出 (?=\/|$) 而不是简单的 $

@magicdawn
Copy link
Owner Author

默认的 respond

默认的respond middleware做了哪些事 https://github.com/koajs/koa/blob/master/lib%2Fapplication.js#L180

总的: 根据 ctx.body & ctx.status 来用 ctx.res node原生的response 来响应

@magicdawn
Copy link
Owner Author

favicon

var favicon = require koa-favicon
app.use (favicon ico_path)

@magicdawn
Copy link
Owner Author

trekjs/router 实现分析

Node

  • .prefix : String
  • .lable : String = .prefix[0]
  • .children = []
  • .handler
  • .pnames
  • .findChild = (c)=> { // 找到 label 与 c 相等的children 项, 1st one }

Router

  • .trees = { GET: Node('/'), POST: Node('/') ... }
  • add/insert/find 细说
  • get/post ... -> router.add(GET/POST... , path, fn)

add

@magicdawn
Copy link
Owner Author

koa v2 alpha 1

middleware

use() https://github.com/koajs/koa/blob/2.0.0-alpha.1/lib/application.js#L94
callback() https://github.com/koajs/koa/blob/2.0.0-alpha.1/lib/application.js#L110

  1. 如果使用的是 generator function, 则使用 co.wrap(fn) 作为middleware,

  2. middleware 结构为

    var middleware = function(ctx, next){
      // whatever
    }
    • ctx 为koa request 上下文
    • next = ? 由koa-compose 给出, 简单来说 next() 返回下一个middleware执行的Promise
    • 函数, 返回任意值, 使用 Promise.resolve(返回值) 表示当前 middleware的Promise

koa-compose

https://github.com/koajs/compose/blob/3.0.0/index.js#L31

return function (context, next) {
   ...
    return dispatch(0)
    function dispatch(i) {
      ...
      const fn = middleware[i] || next
      try {
        return Promise.resolve(fn(context, function next() {
          return dispatch(i + 1)
        }))
      } catch(err) {
        return Promise.reject(err);
      }
    }
  }

留下基本骨架看看, compose([middleware1, middleware2]) returns 上面那个函数fn, fn() 返回Promise, await 这个Promsie表示第一个middleware已经执行完~

so

middleware = function(ctx, next){
  return new Promise...

  next = function(){
     return dispatch(下一个middleware) -> Promise.resolve( fn( ctx, next ))
  }
  next() 返回的是下一个middleware执行的Promise
}

@magicdawn
Copy link
Owner Author

bodyparser

koa-bodyparser -> co-body -> raw-body 包

multipart/form-data

  • busyboy 基础功能
  • multer 提供中间件
  • koa-multer 是将 ctx.req / ctx.res 传到 multer function 里

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant