<sup id="sjju3"><noscript id="sjju3"></noscript></sup>
    <big id="sjju3"></big>

  • <blockquote id="sjju3"></blockquote>
    <blockquote id="sjju3"></blockquote>

      <td id="sjju3"></td>

      <big id="sjju3"></big>
        <code id="sjju3"><strong id="sjju3"><dl id="sjju3"></dl></strong></code>
      1. 如何从零开始手写Koa2框架

         更新时间:2019年03月22日 10:36:02   作者:xpromise   我要评论

        这篇文章主要介绍了如何从零开始手写Koa2框架,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

        01、介绍

        • Koa-- 基于 Node.js 平台的下一代 web 开发框架
        • Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。
        • 与其对应的 Express 来比,Koa 更?#26377;?#24039;、精?#24120;?#26412;文将带大家从零开始实现 Koa 的源码,从根源上解决大家对 Koa 的困惑
        本文 Koa 版本为 2.7.0, 版本不一样源码可能会有变动

        02、源码目录介绍

        Koa 源码目录截图

        通过源码目录可以知道,Koa主要分为4个部分,分别是:

        • application: Koa 最主要的模块, 对应 app 应用对象
        • context: 对应 ctx 对象
        • request: 对应 Koa 中请求对象
        • response: 对应 Koa 中响应对象

        这4个文件就是 Koa 的全部内容了,其中 application ?#36136;?#20854;中最核心的文件。我们将会从此文件入手,一步步实现 Koa 框架

        03、实现一个基本服务器代码目录

        my-application

        const {createServer} = require('http');
        
        module.exports = class Application {
         constructor() {
         // 初始化中间件数组, 所有中间件函数都会添加到当前数组中
         this.middleware = [];
         }
         // 使用中间件方法
         use(fn) {
         // 将所有中间件函数添加到中间件数组中
         this.middleware.push(fn);
         }
         // 监听端口号方法
         listen(...args) {
         // 使用nodejs的http模块监听端口号
         const server = createServer((req, res) => {
          /*
          处理请求的回调函数,在这里执行了所有中间件函数
          req 是 node 原生的 request 对象
          res 是 node 原生的 response 对象
          */
          this.middleware.forEach((fn) => fn(req, res));
         })
         server.listen(...args);
         }
        }

        index.js

        // 引入自定义模块
        const MyKoa = require('./js/my-application');
        // 创建实例对象
        const app = new MyKoa();
        // 使用中间件
        app.use((req, res) => {
         console.log('中间件函数执行了~~~111');
        })
        app.use((req, res) => {
         console.log('中间件函数执行了~~~222');
         res.end('hello myKoa');
        })
        // 监听端口号
        app.listen(3000, err => {
         if (!err) console.log('服务器启动成功了');
         else console.log(err);
        })

        运行入口文件 index.js 后,通过浏览器输入网址访问 http://localhost:3000/ , 就可以看到结果了~~

        神奇吧!一个最简单的服务器模型就搭建完了。?#27604;?#25105;们这个极简服务器还存在很多问题,接下来让我们一一解决

        04、实现中间件函数的 next 方法

        提取createServer的回调函数,封装成一个callback方法(可?#20174;茫?/p>

        // 监听端口号方法
        listen(...args) {
         // 使用nodejs的http模块监听端口号
         const server = createServer(this.callback());
         server.listen(...args);
        }
        callback() {
         const handleRequest = (req, res) => {
         this.middleware.forEach((fn) => fn(req, res));
         }
         return handleRequest;
        }

        封装compose函数实现next方法

        // 负责执行中间件函数的函数
        function compose(middleware) {
         // compose方法返回值是一个函数,这个函数返回值是一个promise对象
         // 当前函数就是调度
         return (req, res) => {
         // 默?#31995;?#29992;一次,为了执行第一个中间件函数
         return dispatch(0);
         function dispatch(i) {
          // 提取中间件数组的函数fn
          let fn = middleware[i];
          // 如果最后一个中间件也调用了next方法,直接返回一个成功状态的promise对象
          if (!fn) return Promise.resolve();
          /*
          dispatch.bind(null, i + 1)) 作为中间件函数调用的第三个参数,其实就是对应的next
           举个栗子:如果 i = 0 那么 dispatch.bind(null, 1)) 
           --> 也就是如果调用了next方法 ?#23548;?#19978;就是执行 dispatch(1) 
            --> 它利用递归重新进来取出下一个中间件函数接着执行
          fn(req, res, dispatch.bind(null, i + 1))
           --> 这也是为什么中间件函数能有三个参数,在调用时我们传进来了
          */
          return Promise.resolve(fn(req, res, dispatch.bind(null, i + 1)));
         }
         }
        }

        使用compose函数

        callback () {
         // 执行compose方法返回一个函数
         const fn = compose(this.middleware);
         
         const handleRequest = (req, res) => {
         // 调用该函数,返回值为promise对象
         // then方法触发了, 说明所有中间件函数都被调用完成
         fn(req, res).then(() => {
          // 在这里就是所?#20889;?#29702;的函数的最后阶段,可以允许返回响应了~
         });
         }
         
         return handleRequest;
        }

        修改入口文件 index.js 代码

        // 引入自定义模块
        const MyKoa = require('./js/my-application');
        // 创建实例对象
        const app = new MyKoa();
        // 使用中间件
        app.use((req, res, next) => {
         console.log('中间件函数执行了~~~111');
         // 调用next方法,就是调用堆栈中下一个中间件函数
         next();
        })
        app.use((req, res, next) => {
         console.log('中间件函数执行了~~~222');
         res.end('hello myKoa');
         // 最后的next方法没发调用下一个中间件函数,直接返回Promise.resolve()
         next();
        })
        // 监听端口号
        app.listen(3000, err => {
         if (!err) console.log('服务器启动成功了');
         else console.log(err);
        })

        此时我们实现了next方法,最核心的就是compose函数,极简的代码实现了功能,不可?#23478;椋?/p>

        05、处理返回响应

        定义返回响应函数respond

        function respond(req, res) {
         // 获取设置的body数据
         let body = res.body;
         
         if (typeof body === 'object') {
         // 如果是对象,转化成json数据返回
         body = JSON.stringify(body);
         res.end(body);
         } else {
         // 默认其他数据直接返回
         res.end(body);
         }
        }

        callback中调用

        callback() {
         const fn = compose(this.middleware);
         
         const handleRequest = (req, res) => {
         // 当中间件函数全部执行完毕时,会触发then方法,从而执行respond方法返回响应
         const handleResponse = () => respond(req, res);
         fn(req, res).then(handleResponse);
         }
         
         return handleRequest;
        }

        修改入口文件 index.js 代码

        // 引入自定义模块
        const MyKoa = require('./js/my-application');
        // 创建实例对象
        const app = new MyKoa();
        // 使用中间件
        app.use((req, res, next) => {
         console.log('中间件函数执行了~~~111');
         next();
        })
        app.use((req, res, next) => {
         console.log('中间件函数执行了~~~222');
         // 设置响应内容,由框架负责返回响应~
         res.body = 'hello myKoa';
        })
        // 监听端口号
        app.listen(3000, err => {
         if (!err) console.log('服务器启动成功了');
         else console.log(err);
        })

        此时我们就能根据不同响应内容做出处理了~?#27604;?#36824;是比较简单的,可以接着去扩展~

        06、定义 Request 模块

        // 此模块需要npm下载
        const parse = require('parseurl');
        const qs = require('querystring');
        
        module.exports = {
         /**
         * 获取请求头信息
         */
         get headers() {
         return this.req.headers;
         },
         /**
         * 设?#20204;?#27714;头信息
         */
         set headers(val) {
         this.req.headers = val;
         },
         /**
         * 获取查询字符串
         */
         get query() {
         // 解析查询字符串参数 --> key1=value1&key2=value2
         const querystring = parse(this.req).query;
         // 将其解析为对象返回 --> {key1: value1, key2: value2}
         return qs.parse(querystring);
         }
        }

        07、定义 Response 模块

        module.exports = {
         /**
         * 设置响应头的信息
         */
         set(key, value) {
         this.res.setHeader(key, value);
         },
         /**
         * 获取响应状态码
         */
         get status() {
         return this.res.statusCode;
         },
         /**
         * 设置响应状态码
         */
         set status(code) {
         this.res.statusCode = code;
         },
         /**
         * 获取响应体信息
         */
         get body() {
         return this._body;
         },
         /**
         * 设置响应体信息
         */
         set body(val) {
         // 设置响应体内容
         this._body = val;
         // 设置响应状态码
         this.status = 200;
         // json
         if (typeof val === 'object') {
          this.set('Content-Type', 'application/json');
         }
         },
        }

        08、定义 Context 模块

        // 此模块需要npm下载
        const delegate = require('delegates');
        
        const proto = module.exports = {};
        
        // 将response对象上的属性/方法克隆到proto上
        delegate(proto, 'response')
         .method('set') // 克隆普通方法
         .access('status') // 克隆带有get和set描述符的方法
         .access('body') 
        
        // 将request对象上的属性/方法克隆到proto上
        delegate(proto, 'request')
         .access('query')
         .getter('headers') // 克隆带有get描述符的方法

        09、揭秘 delegates 模块

        module.exports = Delegator;
        
        /**
         * 初始化一个 delegator.
         */
        function Delegator(proto, target) {
         // this必须指向Delegator的实例对象
         if (!(this instanceof Delegator)) return new Delegator(proto, target);
         // 需要克隆的对象
         this.proto = proto;
         // 被克隆的目标对象
         this.target = target;
         // 所有普通方法的数组
         this.methods = [];
         // 所?#20889;?#26377;get描述符的方法数组
         this.getters = [];
         // 所?#20889;?#26377;set描述符的方法数组
         this.setters = [];
        }
        
        /**
         * 克隆普通方法
         */
        Delegator.prototype.method = function(name){
         // 需要克隆的对象
         var proto = this.proto;
         // 被克隆的目标对象
         var target = this.target;
         // 方法添加到method数组中
         this.methods.push(name);
         // 给proto添加克隆的属性
         proto[name] = function(){
         /*
          this指向proto, 也就是ctx
          举个栗子:ctx.response.set.apply(ctx.response, arguments)
          arguments对应实参列表,刚好与apply方法传参一致
          执行ctx.set('key', 'value') ?#23548;?#19978;相当于执行 response.set('key', 'value')
         */
         return this[target][name].apply(this[target], arguments);
         };
         // 方便链式调用
         return this;
        };
        
        /**
         * 克隆带有get和set描述符的方法.
         */
        Delegator.prototype.access = function(name){
         return this.getter(name).setter(name);
        };
        
        /**
         * 克隆带有get描述符的方法.
         */
        Delegator.prototype.getter = function(name){
         var proto = this.proto;
         var target = this.target;
         this.getters.push(name);
         // 方法可以为一个已经存在的对象设置get描述符属性
         proto.__defineGetter__(name, function(){
         return this[target][name];
         });
        
         return this;
        };
        
        /**
         * 克隆带有set描述符的方法.
         */
        Delegator.prototype.setter = function(name){
         var proto = this.proto;
         var target = this.target;
         this.setters.push(name);
         // 方法可以为一个已经存在的对象设置set描述符属性
         proto.__defineSetter__(name, function(val){
         return this[target][name] = val;
         });
        
         return this;
        };

        10、使用 ctx 取代 req 和 res

        修改 my-application

        const {createServer} = require('http');
        const context = require('./my-context');
        const request = require('./my-request');
        const response = require('./my-response');
        
        module.exports = class Application {
         constructor() {
         this.middleware = [];
         // Object.create(target) 以target对象为原型, 创建新对象, 新对象原型有target对象的属性和方法
         this.context = Object.create(context);
         this.request = Object.create(request);
         this.response = Object.create(response);
         }
         
         use(fn) {
         this.middleware.push(fn);
         }
         
         listen(...args) {
         // 使用nodejs的http模块监听端口号
         const server = createServer(this.callback());
         server.listen(...args);
         }
         
         callback() {
         const fn = compose(this.middleware);
         
         const handleRequest = (req, res) => {
          // 创建context
          const ctx = this.createContext(req, res);
          const handleResponse = () => respond(ctx);
          fn(ctx).then(handleResponse);
         }
         
         return handleRequest;
         }
         
         // 创建context 上下文对象的方法
         createContext(req, res) {
         /*
          凡是req/res,就是node原生对象
          凡是request/response,就是自定义对象
          这是实现互相挂载引用,从而在?#25105;?#23545;象上都能获取其他对象的方法
          */
         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;
         
         return context;
         }
        }
        // 将原来使用req,res的地方改用ctx
        function compose(middleware) {
         return (ctx) => {
         return dispatch(0);
         function dispatch(i) {
          let fn = middleware[i];
          if (!fn) return Promise.resolve();
          return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)));
         }
         }
        }
        
        function respond(ctx) {
         let body = ctx.body;
         const res = ctx.res;
         if (typeof body === 'object') {
         body = JSON.stringify(body);
         res.end(body);
         } else {
         res.end(body);
         }
        }

        修改入口文件 index.js 代码

        // 引入自定义模块
        const MyKoa = require('./js/my-application');
        // 创建实例对象
        const app = new MyKoa();
        // 使用中间件
        app.use((ctx, next) => {
         console.log('中间件函数执行了~~~111');
         next();
        })
        app.use((ctx, next) => {
         console.log('中间件函数执行了~~~222');
         // 获取请求头参数
         console.log(ctx.headers);
         // 获取查询字符串参数
         console.log(ctx.query);
         // 设置响应头信息
         ctx.set('content-type', 'text/html;charset=utf-8');
         // 设置响应内容,由框架负责返回响应~
         ctx.body = '<h1>hello myKoa</h1>';
        })
        // 监听端口号
        app.listen(3000, err => {
         if (!err) console.log('服务器启动成功了');
         else console.log(err);
        })
        到这里已经写完了 Koa 主要代码,有一句古话 - 看万遍代码不如写上一遍。 还等什么,赶紧写上一遍吧~
        当你能够写出来,再去阅读源码,你会发现源码如此简单~

        以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

        相关文章

        • node.js中的fs.fsync方法使用说明

          node.js中的fs.fsync方法使用说明

          这篇文章主要介绍了node.js中的fs.fsync方法使用说明,本文介绍了fs.fsync的方法说明、语法、接收参数、使用实例和实现源码,需要的朋友可以参考下
          2014-12-12
        • Node.js安装教程和NPM包管理器使用详解

          Node.js安装教程和NPM包管理器使用详解

          这篇文章主要介绍了Node.js安装教程和NPM包管理器使用详解,安?#23433;?#20998;讲解了Windows、和MAC OS下的安装图解,并介绍了Linux下的源码安装方法,最后对NPM包管理器做了详细介绍,需要的朋友可以参考下
          2014-08-08
        • node.js中的path.resolve方法使用说明

          node.js中的path.resolve方法使用说明

          这篇文章主要介绍了node.js中的path.resolve方法使用说明,本文介绍了path.resolve的方法说明、接收参数、语法、使用实例和实现源码,需要的朋友可以参考下
          2014-12-12
        • 全面解析Node.js 8 重要功能和修复

          全面解析Node.js 8 重要功能和修复

          5月30日12点,Node.js 8正式发布了,这个版本具有一系列新功能和性能改进,并且这些功能和改进将获得长期支持(LTS)。下面就来介绍Node.js 8版本中最重要的功能和修复
          2017-06-06
        • node thread.sleep实现示例

          node thread.sleep实现示例

          这篇文章主要介绍了node thread.sleep实现示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
          2018-06-06
        • node.js中的require使用详解

          node.js中的require使用详解

          本文通过几个具体实例来分析讲解了node.js中require的使用方法,非常的详尽,这里推荐给大家
          2014-12-12
        • 详解Windows下安装Nodejs步骤

          详解Windows下安装Nodejs步骤

          本篇文章主要介绍了详解Windows下安装Nodejs步骤,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
          2017-05-05
        • Node.js的项目构建工具Grunt的安装与配?#23186;?#31243;

          Node.js的项目构建工具Grunt的安装与配?#23186;?#31243;

          Grunt是为Node打造的项目构建工具,相当于C/C++世界中的makefile,可以执行像压缩、编译、单元测试、代码检查以及打包发布的任务,下面我们就来一起看一下Node.js的项目构建工具Grunt的安装与配?#23186;?#31243;:
          2016-05-05
        • 使用node打造自己的命令行工具方法教程

          使用node打造自己的命令行工具方法教程

          这篇文章主要介绍了使用node打造自己的命令行工具方法教程,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
          2018-03-03
        • 详解通过源码解析Node.js中cluster模块的主要功能实现

          详解通过源码解析Node.js中cluster模块的主要功能实现

          这篇文章主要介绍了详解通过源码解析Node.js中cluster模块的主要功能实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
          2018-05-05

        最新评论

        2018白小姐一肖中特马
        <sup id="sjju3"><noscript id="sjju3"></noscript></sup>
        <big id="sjju3"></big>

      2. <blockquote id="sjju3"></blockquote>
        <blockquote id="sjju3"></blockquote>

          <td id="sjju3"></td>

          <big id="sjju3"></big>
            <code id="sjju3"><strong id="sjju3"><dl id="sjju3"></dl></strong></code>
          1. <sup id="sjju3"><noscript id="sjju3"></noscript></sup>
            <big id="sjju3"></big>

          2. <blockquote id="sjju3"></blockquote>
            <blockquote id="sjju3"></blockquote>

              <td id="sjju3"></td>

              <big id="sjju3"></big>
                <code id="sjju3"><strong id="sjju3"><dl id="sjju3"></dl></strong></code>