美文网首页
koajs 源码解析

koajs 源码解析

作者: 没有颜色的菜 | 来源:发表于2019-08-18 18:56 被阅读0次

    前言

    又是一周过去了,常规学习不能断!但是选择什么主题呢?一时间不知道选什么好,于是又想起简单的 koajs 非常愉快的就选择他了 https://koajs.com/,了解一下?

    他是个什么东西呢?

    Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。

    hello world

    首先新建一个 node 项目,其实很简单,只需要一个 package.json 文件,

    {
      "name": "koa-hello",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "start": "node src/index.js"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "koa": "^2.7.0"
      }
    }
    

    然后执行

    npm i koa
    

    代码 index.js 文件,新建一个 koa 实例,使用 app.use 写一个 async 方法,设置 ctx.body 的值就可以了。最后使用 app.listen 启动。

    const Koa = require('koa');
    const app = new Koa();
    
    app.use(async ctx => {
        ctx.body = 'Hello World';
    });
    
    app.listen(3000);
    

    这样的话,一个 web 服务器就搭建好了,访问 http://localhost:3000/ 就会得到 hello world 返回结果了。你可以尝试更改字段从而得到不同的返回结果。

    源码解析

    koa 的源码只有四个文件,不包含其他引用的话

     .
    ├── History.md
    ├── LICENSE
    ├── Readme.md
    ├── lib
    │   ├── application.js
    │   ├── context.js
    │   ├── request.js
    │   └── response.js
    └── package.json
    

    主入口可以在 package.json 的 main 中得到,是 application.js,暂时先知道 middleware 是中间接,通常一个请求过来就会依次执行中间件的方法。

    构造函数

    module.exports = class Application extends Emitter {
      /**
       * Initialize a new `Application`.
       *
       * @api public
       */
    
      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);
        if (util.inspect.custom) {
          this[util.inspect.custom] = this.inspect;
        }
      }
    }
    

    app.use 其实就是添加一个中间件,我们通常使用 async 的函数,generator 被抛弃了!

      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;
      }
    

    app.listen 创建一个服务器,监听 3000 端口,http.createServer 是 node 的服务器。

      listen(...args) {
        debug('listen');
        const server = http.createServer(this.callback());
        return server.listen(...args);
      }
    

    callback 是提供一个函数,所有请求都会走到这个函数里面进行处理。每次请求过来都会调用这个函数,所以,我们可以看到,每次请求都会创建一个 ctx 的对象。
    compose 的作用就是将所有的中间件整合成一个函数,使用 next 函数继续调用下一个函数。

      callback() {
        const fn = compose(this.middleware);
    
        if (!this.listenerCount('error')) this.on('error', this.onerror);
    
        const handleRequest = (req, res) => {
          const ctx = this.createContext(req, res);
          return this.handleRequest(ctx, fn);
        };
    
        return handleRequest;
      } 
    

    初始化 ctx 对象,这里 this.request 将会把原生的 request 参数进行解析,方便我们进行相关参数获取。

      /**
       * Initialize a new context.
       *
       * @api private
       */
    
      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.state = {};
        return context;
      }
    

    比如我们之后就可以使用
    ** ctx.query.key ** 来获取 http://localhost:3000?key=value,为什么可以使用 ctx.query 又可以获取参数呢,这个要靠 Object.create 的本事了,它相当于创造了一个对象,继承了原来的对象,而 this.request 有 query 的参数,而最为重要的是 this.context = Object.create(context); context 委托(使用了 Delegator)了这些 request 的相关属性和方法。【第一次体会到 js 委托,以前知识听说不知道是啥】

    /**
     * Request delegation.
     */
    
    delegate(proto, 'request')
      .access('method')
      .access('query')
      .access('path')
      .access('url')
      ....... // 省略
    

    handleRequest 请求处理,fnMiddleware 就是所有的中间件,

      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);
      }
    

    调用完中间件以后,就执行 handleResponse 将数据返回,返回数据也就是将 ctx.body 拿出来,使用 response.end 返回数据,返回时,会对数据进行处理,在最后面可以体会到~

    /**
     * Response helper.
     */
    
    function respond(ctx) {
      // allow bypassing koa
      if (false === ctx.respond) return;
    
      if (!ctx.writable) return;
    
      const res = ctx.res;
      let body = ctx.body;
      const code = ctx.status;
    
      // ignore body
      if (statuses.empty[code]) {
        // strip headers
        ctx.body = null;
        return res.end();
      }
    
      if ('HEAD' == ctx.method) {
        if (!res.headersSent && isJSON(body)) {
          ctx.length = Buffer.byteLength(JSON.stringify(body));
        }
        return res.end();
      }
    
      // status body
      if (null == body) {
        if (ctx.req.httpVersionMajor >= 2) {
          body = String(code);
        } else {
          body = ctx.message || String(code);
        }
        if (!res.headersSent) {
          ctx.type = 'text';
          ctx.length = Buffer.byteLength(body);
        }
        return res.end(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);
    }
    

    到这里,基本的请求已经清楚了~~

    End

    再来看一眼最简单的 http server 代码,对比一下,比 koa 代码的 hello world 相比并没有多复杂

    var http = require('http');
    http.createServer(function (req, res) {
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.write('Hello World!');
      res.end();
    }).listen(8080);
    

    但是,获取参数,使用路由等等插件,koa 生态做了很多,非常方便,快来体验吧!

    相关文章

      网友评论

          本文标题:koajs 源码解析

          本文链接:https://www.haomeiwen.com/subject/gufdsctx.html