美文网首页
Node Http与Koa框架简介

Node Http与Koa框架简介

作者: 大的像个坑 | 来源:发表于2018-10-15 15:13 被阅读0次

    一、Node简介

    1. Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。

    2. Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,基于回调实现的异步编程,使其轻量又高效。 例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。由于JavaScript是单线程的语言,所以并不适合处理长时间占用cup资源的任务,比如大型计算型任务。

    二、Http模块

    1. 要使用 HTTP 服务器与客户端,需要 require('http')。

    2. http.Agent类 负责为 HTTP 客户端管理连接的持续与复用。 它为一个给定的主机与端口维护着一个等待请求的队列,且为每个请求重复使用一个单一的 socket 连接直到队列为空,此时 socket 会被销毁或被放入一个连接池中,在连接池中等待被有着相同主机与端口的请求再次使用。 是否被销毁或被放入连接池取决于 keepAlive

    3. http.ClientRequest 类 该类可作为代理请求时的请求对象的类。该对象在 http.request() 内部被创建并返回。 它表示着一个正在处理的请求,其请求头已进入队列。 请求头仍可使用 setHeader(name, value)getHeader(name)removeHeader(name) API 进行修改。 实际的请求头会与第一个数据块一起发送或当调用 request.end() 时发送。、

    4. http.Server类 该类是对于net.Server类类的继承,这个类用于创建 TCP 或 IPC server。

    5. http.ServerResponse 类 该对象在 HTTP 服务器内部被创建。 它作为第二个参数被传入 'request' 事件。这个类实现了(而不是继承自)可写流 接口。

    6. http.IncomingMessage 类 该对象由 http.Serverhttp.ClientRequest 创建,并作为第一个参数分别递给 'request''response' 事件。 它可以用来访问响应状态、消息头、以及数据。它实现了 可读流 接口,还有以下额外的事件、方法、以及属性。

    7、http.createServer方法 该方法为常用的Server创建方法:

    //方法定义
    function createServer(requestListener?: (request: IncomingMessage, response: ServerResponse) => void): Server;
    
    //创建服务
    const http = require('http');
    
    const Server = http.createServer((req, res) => {
      res.writeHead(200,{'Content-Type': 'application/json;charset=utf-8;'});
      res.write('{text: "Hello World!"}');
      res.end();
    }).listen(3000,(err) => {
      console.log('Server is running on port 3000');
    })
    

    返回一个http.Server的实例,requestListener是一个自动添加到'request'事件的方法。

    三、Koa框架简介

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

    2. 创建Koa应用:

    const Koa = require('koa');
    const app = new Koa();
    
    app.use(async ctx => {
      ctx.body = 'Hello World';
    });
    
    app.listen(3000);
    
    1. Koa 中间件以更传统的方式级联,您可能习惯使用类似的工具 - 之前难以让用户友好地使用 node 的回调。然而,使用 async 功能,我们可以实现 “真实” 的中间件。对比 Connect 的实现,通过一系列功能直接传递控制,直到一个返回,Koa 调用“下游”,然后控制流回“上游”。下面以 “Hello World” 的响应作为示例,当请求开始时首先请求流通过 x-response-time 和 logging 中间件,然后继续移交控制给 response 中间件。当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。
    // logger
    
    app.use(async (ctx, next) => {
      await next();
      const rt = ctx.response.get('X-Response-Time');
      console.log(`${ctx.method} ${ctx.url} - ${rt}`);
    });
    
    // x-response-time
    
    app.use(async (ctx, next) => {
      const start = Date.now();
      await next();
      const ms = Date.now() - start;
      ctx.set('X-Response-Time', `${ms}ms`);
    });
    
    // response
    
    app.use(async ctx => {
      ctx.body = 'Hello World';
    });
    
    app.listen(3000);
    
    1. app.listen(...)方法是以下方法的语法糖:
    const http = require('http');
    const Koa = require('koa');
    const app = new Koa();
    http.createServer(app.callback()).listen(...);
    
    1. app.callback()返回适用于 http.createServer() 方法的回调函数来处理请求。你也可以使用此回调函数将 koa 应用程序挂载到 Connect/Express 应用程序中
    //处理请求,合并中间件与创建请求上下文对象
    class Application extends EventEmitter {
        //...other code
        callback() {
          //合并中间件
          const fn = compose(this.middleware);
    
          //绑定错误处理
          if (!this.listenerCount('error')) this.on('error', this.onerror);
    
          const handleRequest = (req, res) => {
            //封装req,res为ctx对象
            const ctx = this.createContext(req, res);
            return this.handleRequest(ctx, fn);
          };
          
          //返回一个(req,res) => {...}的function
          return handleRequest;
        }
        //...other code
    }
    
    //实现中间件级联的核心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!')
      }
    
      /**
       * @param {Object} context
       * @return {Promise}
       * @api public
       */
    
      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, dispatch.bind(null, i + 1)));
          } catch (err) {
            //通过嵌套Promise来实现err的冒泡
            return Promise.reject(err)
          }
        }
      }
    }
    
    1. app.use(function)将给定的中间件方法添加到此应用程序。
    //检查中间件类型,并传入中间件数组
      use(fn) {
        if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
        //将生成器函数转换为async函数,并传入中间件数组
        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;
      }
    
    1. 默认情况下,将所有错误输出到 stderr,除非 app.silent 为 true。 当 err.status 是 404 或 err.expose 是 true 时默认错误处理程序也不会输出错误。 要执行自定义错误处理逻辑,如集中式日志记录,您可以添加一个 “error” 事件侦听器:
    app.on('error', err => {
      log.error('server error', err)
    });
    
    1. 源代码很简洁核心代码如下:
    
    'use strict';
    
    /**
     * Module dependencies.
     */
    
    const isGeneratorFunction = require('is-generator-function');
    const debug = require('debug')('koa:application');
    const onFinished = require('on-finished');
    const response = require('./response');
    const compose = require('koa-compose');
    const isJSON = require('koa-is-json');
    const context = require('./context');
    const request = require('./request');
    const statuses = require('statuses');
    const Emitter = require('events');
    const util = require('util');
    const Stream = require('stream');
    const http = require('http');
    const only = require('only');
    const convert = require('koa-convert');
    const deprecate = require('depd')('koa');
    
    /**
     * Expose `Application` class.
     * Inherits from `Emitter.prototype`.
     */
    
    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;
        }
      }
    
      /**
       * Shorthand for:
       *
       *    http.createServer(app.callback()).listen(...)
       *
       * @param {Mixed} ...
       * @return {Server}
       * @api public
       */
    
      //创建http服务器,接收传入参数,监听端口
      listen(...args) {
        debug('listen');
    
        //生成一个服务器对象,并调用Application类的callback方法返回一个(req,res) => {}函数对象
        const server = http.createServer(this.callback());
        return server.listen(...args);
      }
    
      /**
       * Return JSON representation.
       * We only bother showing settings.
       *
       * @return {Object}
       * @api public
       */
    
      toJSON() {
        return only(this, [
          'subdomainOffset',
          'proxy',
          'env'
        ]);
      }
    
      /**
       * Inspect implementation.
       *
       * @return {Object}
       * @api public
       */
    
       //检查器
      inspect() {
        return this.toJSON();
      }
    
      /**
       * Use the given middleware `fn`.
       *
       * Old-style middleware will be converted.
       *
       * @param {Function} fn
       * @return {Application} self
       * @api public
       */
    
       //检查中间件类型
      use(fn) {
        if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
        //将生成器函数转换为async函数,并传入中间件数组
        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;
      }
    
      /**
       * Return a request handler callback
       * for node's native http server.
       *
       * @return {Function}
       * @api public
       */
    
       //处理请求,合并中间件与创建请求上下文对象
      callback() {
        //合并中间件
        const fn = compose(this.middleware);
    
        //绑定错误处理
        if (!this.listenerCount('error')) this.on('error', this.onerror);
    
        const handleRequest = (req, res) => {
          //封装req,res为ctx对象
          const ctx = this.createContext(req, res);
          return this.handleRequest(ctx, fn);
        };
    
        return handleRequest;
      }
    
      /**
       * Handle request in callback.
       *
       * @api private
       */
    
       //请求处理句柄
      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);
      }
    
      /**
       * Initialize a new context.
       *
       * @api private
       */
    
       //创建createContext对象,交叉挂载req,res,ctx对象
      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;
      }
    
      /**
       * Default error handler.
       *
       * @param {Error} err
       * @api private
       */
    
       //错误处理句柄
      onerror(err) {
        if (!(err instanceof Error)) throw new TypeError(util.format('non-error thrown: %j', 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();
      }
    };
    
    /**
     * Response helper.
     */
    
    //响应句柄
    function respond(ctx) {
      // allow bypassing koa
      if (false === ctx.respond) return;
    
      const res = ctx.res;
      if (!ctx.writable) return;
    
      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) {
        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);
    }
    
    1. 通过delegate方法将ctx的访问代理带原生request、response对象上去,提供了遍历的方法
    /**
    * Response delegation.
    */
    
    delegate(proto, 'response')
     .method('attachment')
     .method('redirect')
     .method('remove')
     .method('vary')
     .method('set')
     .method('append')
     .method('flushHeaders')
     .access('status')
     .access('message')
     .access('body')
     .access('length')
     .access('type')
     .access('lastModified')
     .access('etag')
     .getter('headerSent')
     .getter('writable');
    
    /**
    * Request delegation.
    */
    
    delegate(proto, 'request')
     .method('acceptsLanguages')
     .method('acceptsEncodings')
     .method('acceptsCharsets')
     .method('accepts')
     .method('get')
     .method('is')
     .access('querystring')
     .access('idempotent')
     .access('socket')
     .access('search')
     .access('method')
     .access('query')
     .access('path')
     .access('url')
     .access('accept')
     .getter('origin')
     .getter('href')
     .getter('subdomains')
     .getter('protocol')
     .getter('host')
     .getter('hostname')
     .getter('URL')
     .getter('header')
     .getter('headers')
     .getter('secure')
     .getter('stale')
     .getter('fresh')
     .getter('ips')
     .getter('ip');
    
    1. 相关文档
      Node.js官方网站
      Koa (koajs) -- 基于 Node.js 平台的下一代 web 开发框架 | Koajs 中文文档

    相关文章

      网友评论

          本文标题:Node Http与Koa框架简介

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