美文网首页
基于koa2.x实现静态资源服务器

基于koa2.x实现静态资源服务器

作者: 空无一码 | 来源:发表于2018-12-29 08:42 被阅读123次

    详细很多人都看过阮一峰的 koa框架教程 , 非常通俗易懂的入门教程,但对于koa 框架的一些内部原理,特别是中间件机制不是很了解。因此,本文想通过 基于koa-static第三方中间件 去搭建静态资源服务器并分析其源码,来了解 koa 这个node流行web框架的一些内部机制。

    学习准备

    在开始前,先告诉大家需要掌握的js web开发基础知识和如何搭建开发环境。

    基础知识

    在开始学习前,希望您已经有了一定的web开发基础,可通过以下推荐的学习资料,来掌握本文所需的必备前端基础知识:

    搭建环境

    因为本人用的是Mac系统,开发的演示在Mac系统下进行,不过使用windows系统也差不多,都需要安装node、npm包管理器,和我推荐使用的vscode。

    • 安装node:不管是windows还是Mac系统,安装方式都有很多(我一般使用nvm),不过作为教程,我只提供去node官网https://nodejs.org/en/download/直接下载安装的方式,其他方式网上都能搜到。
    • 安装npm:只要node版本不是特别低,在安装node的时候就一起安装npm了,当然也可单独安装yarn这个npm包管理器,不过高版本的npm和yarn相差不大,使用npm即可。
    • 安装vscode: 无论使用什么系统,直接去官网https://code.visualstudio.com/Download找到对应的系统版本安装即可,当然也可以选择任何自己使用的编辑器。

    简单入门例子

    本节我们将从0实现一个最简单的koa web网站,输入一个IP地址加端口号,就能返回一个特别简单的静态网站。

    • 初始化工程:
    mkdir staticServerByKoa // 创建工程目录
    cd staticServerByKoa // 进入目录
    npm init -f // 初始化node项目
    

    可以看到目录下生成了一个package.json 文件,定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)等,内容如下:

    {
      "name": "staticServerByKoa",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC"
    }
    
    • 创建入口文件
    touch index.js // 创建文件
    
    • 编辑文件
    vi index.js // 使用终端的vi编辑器,也可以直接使用vscode等界面编辑器。
    

    在英文输入法下按一下 i 键即进入编辑模式,输入如下内容:

    const Koa = require('koa');  // 引入koa框架
    const app = new Koa();
    // ctx 为Koa 提供的 Context 对象,表示一次对话的上下文(包括 HTTP 请求和 HTTP 回复),通过操作ctx,就可以控制返回给用户的内容。
    app.use(ctx => {
      // 该属性就是发送给用户的内容。
      ctx.response.body = '写代码很快乐!';
    });
    
    app.listen(8888);
    console.log('恭喜你,服务器启动成功:复制 http://localhost:8888/ 到浏览器即可访问');
    

    输完后,按esc进入指令模式,然后按 shift + : 两个键进入命令行模式,输入 wq 保存退出。

    • 添加启动脚本: 在package.json 文件中的scripts 部分加入 "start": "node index", 如下所示:
    {
      "name": "staticServerByKoa",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "start": "node index",
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "koa": "^2.6.2"
      }
    }
    
    • 启动项目,在项目根目录下输入:
    npm start
    

    不出意外,会报错。。。(因为还没有安装koa的依赖)

    • 安装koa依赖,在项目根目录下输入:
    npm i koa // 安装成功后可看到 package.json 文件 多了一个dependencies的配置项,里面包含了koa这个名称和所用的版本
    
    • 再次启动项目,服务成功后就会在终端打印出如下信息:
    > staticServerByKoa@1.0.0 start /Users/xian2/immoc/staticServerByKoa
    > node index
    
    恭喜你,服务器启动成功:复制 http://localhost:8888/ 到浏览器即可访问
    

    把终端中的 http://localhost:8888/ 复制到浏览器即可看到网页显示:写代码很快乐,至此,一个简单的入门例子就实现了。

    升级入门例子

    在上一节我们需要手动开启浏览器,并且更改文件后还需要手动重启服务器,同时页面太丑伤不起,所以本节进行优化:

    自动开启浏览器

    • 将 index.js文件改为:
    const Koa = require('koa');  // 引入koa框架
    const cp = require('child_process'); // 用来创建子进程
    const app = new Koa();
    // ctx 为Koa 提供的 Context 对象,表示一次对话的上下文(包括 HTTP 请求和 HTTP 回复),通过操作ctx,就可以控制返回给用户的内容。
    app.use(ctx => {
      // 该属性就是发送给用户的内容。
      ctx.response.body = '写代码很快乐!';
    });
    app.listen(8888);
    cp.exec('open http://localhost:8888/'); // 自动打开浏览器
    console.log('恭喜你,服务器启动成功:复制 http://localhost:8888/ 到浏览器即可访问');
    

    文件更新后自动重启

    • 安装 nodemon:
    // --save-dev 是为了让nodemon 配置到开发环境的依赖项即devDependencies中,因为生产环境不需要用它
    npm i --save-dev nodemon // 它会监测项目中的所有文件,一旦发现文件有改动,会自动重启应用
    
    • 修改package.json 文件中scripts的start脚本为:
     "start": "nodemon index",
    

    此时,我们把index.js中的“带代码很快乐”改成“不聪明的码农,写代码很苦逼”,就会重新打开浏览器并显示最新内容。

    • 优化页面内容,将内容中间件改为:
    app.use(ctx => {
      // 该属性就是发送给用户的内容。
      ctx.response.type = 'html';
      ctx.response.body = '<h1 style="color: red;height: 60px; background-color: black;">导航栏</h1>';
    });
    

    此时就可以返回 最常见的html内容了,但是写起来很不便,需要再次优化。

    • 再次优化,将内容中间件改为:
    app.use(ctx => {
      // 该属性就是发送给用户的内容。
      ctx.response.type = 'html';
      ctx.response.body = fs.createReadStream('index.html');
    });
    

    不出意外,会报错,因为我们没有引入fs, 在前面加入:

    const fs = require('fs');
    

    此时,虽然不报错,但 页面显示 Not Found,因为我们没有 创建index.html,
    创建 index.html,并输入以下内容:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>学习koa</title>
      </head>
      <body>
        <h1>学习真快乐!</h1>
      </body>
    </html>
    

    此时,每改动一次html文件,在浏览器刷新即可看到最新内容。并且添加内容和样式就方便很多了。

    使用 koa-static 搭建静态服务器

    • 在根目录下新建index.css文件,输入如下内容:
    .header-wrap {
      color: red;
    }
    
    • 更改index.html文件为:
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>学习koa</title>
        <link href="index.css" rel="stylesheet">
      </head>
      <body>
        <h1 class="header-wrap">学习真快乐!</h1>
      </body>
    </html>
    

    此时,重启服务器,发现页面内容的文字并没有变成红色。这是因为我们没有做对css文件格式的处理,此时如果我们使用图片,也会不能正常显示。那么我们需要一个个去处理吗?当然可以,但是这样开发效率就太低了,一般我们会选择koa-static去实现。不过作为学习,我们不仅需要学会用,更需要掌握如何学习第三方依赖库的方法。

    • 在根目录下创建static目录,并把index.css和index.html移到该目录。
    • 修改index.js,如下所示:
    const Koa = require('koa');  // 引入koa框架
    const cp = require('child_process'); // 用来创建子进程
    const path = require('path');
    const KoaStatic = require('koa-static');
    
    const app = new Koa();
    
    app.use(KoaStatic(path.join( __dirname, './static')));
    
    app.listen(8888);
    cp.exec('open http://localhost:8888/'); // 自动打开浏览器
    
    console.log('恭喜你,服务器启动成功:复制 http://localhost:8888/ 到浏览器即可访问');
    
    • 报错的话,极有可能是因为没有安装koa-static(小白常见错误),执行如下命令:
    npm i koa-static
    
    • 重新启动 服务器后,我们就能看到 页面文字显示为红色了,并且也可以正常使用图片了。

    koa-static 源码分析

    细心的人会发现,我们只是输入http://localhost:8888/,并没有输入index.html, 浏览器自动打开却显示了它的内容,这是为什么呢?想了解原因,我们就需要去了解源码。

    学习源码的方式有很多种,我们可以去npm官网直接搜该库,一般都会托管在github, 我们可以直接下载来看,还可以参与该库的开发维护。如果只是简单看看,我们通过编辑器在项目根目录的node_modules找到该依赖库即可。

    • 查看其相关依赖,一般通过其package.json 文件中的dependencies查看:
      "dependencies": {
        "debug": "^3.1.0",
        "koa-send": "^5.0.0"
      },
    

    我们看到它主要依赖koa-send,一会我们也需要去查看一下koa-send。

    • 点开node_modules的koa-static目录,我们看到它只有四个文件,代码只在index.js文件,可以看到它只是对koa-send作了简单封装,涉及到解决我们疑惑的问题主要在如下的几行(30行前后找):
    function serve (root, opts) {
      opts = Object.assign({}, opts)
      opts.root = resolve(root)
      if (opts.index !== false) opts.index = opts.index || 'index.html'
    

    因为我们没有传入opts对象作为koa-static的第二个参数,所以opts为空对象opts, 此时 opts.index的值就被设为了index.html。

    • 我们再点开node_modules的koa-send目录的index.js文件, 找到 如下几行:
      // 大概40多行
      path = path.substr(parse(path).root.length)
      const index = opts.index
      const maxage = opts.maxage || opts.maxAge || 0
    
      // 大概60多行
      if (path === -1) return ctx.throw(400, 'failed to decode')
    
      // index file support
      if (index && trailingSlash) path += index
    
      path = resolvePath(root, path)
    

    可以看到在koa-send首先接收了koa-static调用时传进来的opts参数,然后把默认的index.html文件,加到了请求路径当中,这样在们直接输入http://localhost:8888/就相当于访问http://localhost:8888/index.html文件了。因此,我们可以看到页面显示其内容。

    koa 源码简析

    既然已经掌握了看依赖库源码的方法,我们何不也简单一下koa的源码呢?

    • 点开node_modules的koa目录,包含了如下文件:
    node_modules/koa
    ├── History.md
    ├── LICENSE
    ├── Readme.md
    ├── lib
    │   ├── application.js //入口文件,封装了context,request,response,以及最核心的中间件处理流程。
    │   ├── context.js //处理应用上下文,里面直接封装部分request.js和response.js的方法
    │   ├── request.js // 处理http请求
    │   └── response.js // 处理http响应
    └── package.json
    

    我们看到源码都在lib目录下, koa2.x作为一个web框架,只提供了封装好的HTTP服务,以及基于async/await的中间件容器。用Koa.js想实现大部分Web功能的话,就需要通过中间件来实现,如我们前面用到的koa-static。

    • listen如何实现的?
     listen(...args) {
        debug('listen');
        const server = http.createServer(this.callback());
        return server.listen(...args);
      }
    
    • app.use() 干了啥?
      use(fn) {
        ...省略了各种异常和兼容处理
        if (isGeneratorFunction(fn)) {
          fn = convert(fn); 
          // koa@2中间件只支持 async/await 封装的,如果要使用koa@1基于generator中间件,需要通过中间件koa-convert封装一下才能使用
        }
        this.middleware.push(fn);
        return this;
      }
    
    • callback 源码
      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;
      }
    

    可以看到,在我们实例化 const app = new koa(); 后,执行 app.use() 系列中间件后,会把各中间件添加进 middleware 这个数组,然后再执行app.listen() 的时候,会把callback()回调函数传入 node原生的http模块的createServer()方法,然后在启动服务器以后就会执行callback()中请求、响应、上下文以及中间件的有关逻辑了。更深入的探究,就是基于此一步步分析各个模块的实现了。

    • 中间件简介
      Koa.js 中间件 可为 狭义中间件和广义中间件 两种类型,其区别如下:
      狭义中间件特点
    一般直接被 app.use() 加载
    中间件内请求拦截 request
    中间件内响应拦截 response
    中间件内上下文代理,初始化实例时候挂载代理在app.context上,请求时候挂载代理在ctx上
    

    例如, koa-bodyparser主要是拦截请求后解析出HTTP请求体中的POST数据,而koa-static主要是靠拦截请求和响应,加载静态资源,再挂载到ctx上。

    广义中间件特点

    间接被 app.use() 加载
    间接提供中间件或者子中间件
    其他方式接入koa切面
    

    例如中间koa-router 是先注册路由后形成多个子中间件,后面再封装成一个父中间件提供给app.use()加载,让所有子中间件加载到Koa.js的请求洋葱模型中。

    结语

    本文 通过 从0 到 基于koa-static 一步步 去搭建静态资源服务器和分析其源码,且简单看了看koa2的源码,了解 koa2一些内部机制,特别是掌握了分析源码的一些方法,便于大家后续学习提高。项目有关的代码都托管在GitHub上: https://github.com/yibiankeji/staticServerByKoa.git

    相关文章

      网友评论

          本文标题:基于koa2.x实现静态资源服务器

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