NodeJS笔记

事件视界
事件视界
发布于 2022-09-07 / 378 阅读
24
0

NodeJS笔记

1 简介

NodeJS即一个由JS开发的开源后端程序

2 模块

Node环境中,一个.js文件就称之为一个模块(module)。

'use strict';
var s = 'Hello';
function greet(name) {
    console.log(s + ', ' + name + '!');
}
module.exports = greet;//通过这个语句来暴露greet函数(也可以是对象等)
'use strict';
var greet = require('./hello');//通过这个语句来接收模块内暴露的函数
var s = 'Michael';
greet(s); // Hello, Michael!

在这两个模块中,内部各自使用的变量名和函数名都互不冲突

注:NodeJs暴露了moduleexports两个变量,其中exports是接收module.exports的变量,直接对exports赋值无法改变module.exports,但修改exports的属性可以影响到module.exports,因此一般用module.exports即可

3 基本模块

global——Node.js的全局变量

process——当前Node.js的进程

3.1 fs——文件系统模块

同时提供同步异步方法

'use strict';
//异步读取
var fs = require('fs');

fs.readFile('sample.txt', 'utf-8', function (err, data) {
    //若为二进制文件并不传入编码,则data为一个Buffer对象(一个包含零个或任意个字节的数组)
    if (err) {
        console.log(err);
    } else {
        console.log(data);
    }
});
//同步读取
'use strict';

var fs = require('fs');

var data = fs.readFileSync('sample.txt', 'utf-8');
console.log(data);
//若出错需要使用try...catch来捕获
'use strict';
//写文件
var fs = require('fs');

var data = 'Hello, Node.js';
fs.writeFile('output.txt', data, function (err) {
    if (err) {
        console.log(err);
    } else {
        console.log('ok.');
    }
});
//writeFileSync即写文件的同步方法

stat——获取文件状态(大小,创建时间等)

'use strict';

var fs = require('fs');

fs.stat('sample.txt', function (err, stat) {
    if (err) {
        console.log(err);
    } else {
        // 是否是文件:
        console.log('isFile: ' + stat.isFile());
        // 是否是目录:
        console.log('isDirectory: ' + stat.isDirectory());
        if (stat.isFile()) {
            // 文件大小:
            console.log('size: ' + stat.size);
            // 创建时间, Date对象:
            console.log('birth time: ' + stat.birthtime);
            // 修改时间, Date对象:
            console.log('modified time: ' + stat.mtime);
        }
    }
});
//当然,也有同步方法

大部分时候用异步,因为同步会卡执行进程

3.2 stream——流模块

读取

'use strict';

var fs = require('fs');

// 打开一个流:
var rs = fs.createReadStream('sample.txt', 'utf-8');

rs.on('data', function (chunk) { //data事件,表示可以开始读取
    console.log('DATA:')
    console.log(chunk);
});

rs.on('end', function () { //end事件,表示数据到头
    console.log('END');
});

rs.on('error', function (err) { //error事件,表示出错
    console.log('ERROR: ' + err);
});

写入

'use strict';

var fs = require('fs');

var ws1 = fs.createWriteStream('output1.txt', 'utf-8');
ws1.write('使用Stream写入文本数据...\n');
ws1.write('END.');
ws1.end();

var ws2 = fs.createWriteStream('output2.txt');
ws2.write(new Buffer('使用Stream写入二进制数据...\n', 'utf-8'));
ws2.write(new Buffer('END.', 'utf-8'));
ws2.end();

边读边写:实际上就是复制

'use strict';

var fs = require('fs');
//readable.pipe(writable, { end: false });//如果不希望写入流关闭,则需要设置此项
var rs = fs.createReadStream('sample.txt');
var ws = fs.createWriteStream('copied.txt');

rs.pipe(ws);

3.3 HTTP——服务器程序

一个简易的http服务器

'use strict';

// 导入http模块:
var http = require('http');

// 创建http server,并传入回调函数:
var server = http.createServer(function (request, response) {
    // 回调函数接收request和response对象,
    // 获得HTTP请求的method和url:
    console.log(request.method + ': ' + request.url);
    // 将HTTP响应200写入response, 同时设置Content-Type: text/html:
    response.writeHead(200, {'Content-Type': 'text/html'});
    // 将HTTP响应的HTML内容写入response:
    response.end('

Hello world!

'); }); // 让服务器监听8080端口: server.listen(8080); console.log('Server is running at http://127.0.0.1:8080/');

上面获取到的request.url可以通过parse()来解析

'use strict';

var url = require('url');

console.log(url.parse('http://user:pass@host.com:8080/path/to/file?query=string#hash'));
//结果
/*
Url {
  protocol: 'http:',
  slashes: true,
  auth: 'user:pass',
  host: 'host.com:8080',
  port: '8080',
  hostname: 'host.com',
  hash: '#hash',
  search: '?query=string',
  query: 'query=string',
  pathname: '/path/to/file',
  path: '/path/to/file?query=string',
  href: 'http://user:pass@host.com:8080/path/to/file?query=string#hash' }
  */

实现一个文件服务器:

'use strict';

var
    fs = require('fs'),
    url = require('url'),
    path = require('path'),
    http = require('http');

// 从命令行参数获取root目录,默认是当前目录:
var root = path.resolve(process.argv[2] || '.');

console.log('Static root dir: ' + root);

// 创建服务器:
var server = http.createServer(function (request, response) {
    // 获得URL的path,类似 '/css/bootstrap.css':
    var pathname = url.parse(request.url).pathname;
    // 获得对应的本地文件路径,类似 '/srv/www/css/bootstrap.css':
    var filepath = path.join(root, pathname);
    // 获取文件状态:
    fs.stat(filepath, function (err, stats) {
        if (!err && stats.isFile()) {
            // 没有出错并且文件存在:
            console.log('200 ' + request.url);
            // 发送200响应:
            response.writeHead(200);
            // 将文件流导向response:
            fs.createReadStream(filepath).pipe(response);
        } else {
            // 出错了或者文件不存在:
            console.log('404 ' + request.url);
            // 发送404响应:
            response.writeHead(404);
            response.end('404 Not Found');
        }
    });
});

server.listen(8080);

console.log('Server is running at http://127.0.0.1:8080/');

3.4 crypto——加密算法

为了让运行速度更快,nodejsc/c++实现加密算法后通过cypto暴露以供js使用

3.4.1 MD5和SHA1

const crypto = require('crypto');

const hash = crypto.createHash('md5');//若要用sha1,则把md5改为sha1(也可以用sha256和sha512等等)

// 可任意多次调用update():
hash.update('Hello, world!');
hash.update('Hello, nodejs!');

console.log(hash.digest('hex')); // 7e1977739c748beac0c0fd14fd26a544

3.4.2 Hmac——带秘钥的哈希算法

const crypto = require('crypto');

const hmac = crypto.createHmac('sha256', 'secret-key');//秘钥改变,输出改变

hmac.update('Hello, world!');
hmac.update('Hello, nodejs!');

console.log(hmac.digest('hex')); 

3.4.3 AES——可加解密

需要自己封装好函数以便使用

const crypto = require('crypto');

function aesEncrypt(data, key) {
    const cipher = crypto.createCipher('aes192', key);
    var crypted = cipher.update(data, 'utf8', 'hex');
    crypted += cipher.final('hex');
    return crypted;
}

function aesDecrypt(encrypted, key) {
    const decipher = crypto.createDecipher('aes192', key);
    var decrypted = decipher.update(encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
}

var data = 'Hello, this is a secret message!';
var key = 'Password!';
var encrypted = aesEncrypt(data, key);
var decrypted = aesDecrypt(encrypted, key);

console.log('Plain text: ' + data);
console.log('Encrypted text: ' + encrypted);
console.log('Decrypted text: ' + decrypted);

3.4.4 Diffie-Hellman——秘钥交换协议

不泄露秘钥的情况下协商出秘钥来

const crypto = require('crypto');
//素数选择随机,故输出每次都不一样
// xiaoming's keys:
var ming = crypto.createDiffieHellman(512);
var ming_keys = ming.generateKeys();

var prime = ming.getPrime();
var generator = ming.getGenerator();

console.log('Prime: ' + prime.toString('hex'));
console.log('Generator: ' + generator.toString('hex'));

// xiaohong's keys:
var hong = crypto.createDiffieHellman(prime, generator);
var hong_keys = hong.generateKeys();

// exchange and generate secret:
var ming_secret = ming.computeSecret(hong_keys);
var hong_secret = hong.computeSecret(ming_keys);

// print secret:
console.log('Secret of Xiao Ming: ' + ming_secret.toString('hex'));
console.log('Secret of Xiao Hong: ' + hong_secret.toString('hex'));

3.4.5 RSA——非对称加密算法

分公钥和私钥,公钥可以公开,私钥必须保密

nodejs使用RSA前需要先准备私钥公钥

openssl genrsa -aes256 -out rsa-key.pem 2048 //命令行输入

可以通过其生成的rsa-key.pem来导出解密后的私钥

openssl rsa -in rsa-key.pem -outform PEM -out rsa-prv.pem

通过以下导出原始公钥

openssl rsa -in rsa-key.pem -outform PEM -pubout -out rsa-pub.pem

私钥加密,公钥解密

const
    fs = require('fs'),
    crypto = require('crypto');

// 从文件加载key:
function loadKey(file) {
    // key实际上就是PEM编码的字符串:
    return fs.readFileSync(file, 'utf8');
}

let
    prvKey = loadKey('./rsa-prv.pem'),
    pubKey = loadKey('./rsa-pub.pem'),
    message = 'Hello, world!';

// 使用私钥加密:
let enc_by_prv = crypto.privateEncrypt(prvKey, Buffer.from(message, 'utf8'));
console.log('encrypted by private key: ' + enc_by_prv.toString('hex'));

// 使用公钥解密:
let dec_by_pub = crypto.publicDecrypt(pubKey, enc_by_prv);
console.log('decrypted by public key: ' + dec_by_pub.toString('utf8'));

// 使用公钥加密:
let enc_by_pub = crypto.publicEncrypt(pubKey, Buffer.from(message, 'utf8'));
console.log('encrypted by public key: ' + enc_by_pub.toString('hex'));

// 使用私钥解密:
let dec_by_prv = crypto.privateDecrypt(prvKey, enc_by_pub);
console.log('decrypted by private key: ' + dec_by_prv.toString('utf8'));

3.4.5 证书

也可以处理证书,但是建议交给反代服务器处理

4 WEB开发

4.1 koa

在程序目录下建立package.json文件,描述这个程序要用到哪些包

{
    "name": "hello-koa2",
    "version": "1.0.0",
    "description": "Hello Koa 2 example with async",
    "main": "app.js",
    "scripts": {
        "start": "node app.js"
    },
    "keywords": [
        "koa",
        "async"
    ],
    "author": "Michael Liao",
    "license": "Apache-2.0",
    "repository": {
        "type": "git",
        "url": "https://github.com/michaelliao/learn-javascript.git"
    },
    "dependencies": {
        "koa": "2.0.0"
    }
}

npm install就可以把这个文件里描述的所需包全安装好了

// 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:
const Koa = require('koa');

// 创建一个Koa对象表示web app本身:
const app = new Koa();

// 对于任何请求,app将调用该异步函数处理请求:
app.use(async (ctx, next) => {
    //await next()即执行下一个app.use,如果没有这行语句后续的异步函数(middleware)都不会执行
    await next();
    ctx.response.type = 'text/html';
    ctx.response.body = '

Hello, koa2!

'; }); // 在端口3000监听: app.listen(3000); console.log('app started at port 3000...');

4.1.1 路由处理URL

const Koa = require('koa');
//需要依赖"koa-router": "7.0.0"
// 注意require('koa-router')返回的是函数:
const router = require('koa-router')();//这个多出来的括号是函数调用,相当于const fn_router = require('koa-router');const router = fn_router();

const app = new Koa();

//控制台打印请求地址
app.use(async (ctx, next) => {
    console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
    await next();
});

// add url-route:
router.get('/hello/:name', async (ctx, next) => {
    var name = ctx.params.name;
    ctx.response.body = `

Hello, ${name}!

`; }); router.get('/', async (ctx, next) => { ctx.response.body = '

Index

'; }); // add router middleware: app.use(router.routes());//函数调用 app.listen(3000); console.log('app started at port 3000...');

4.1.2 处理POST请求

nodejskoa提供的request对象都不支持解析post提交的body

//需要添加依赖"koa-bodyparser": "3.2.0"
const bodyParser = require('koa-bodyparser');

4.1.3 逻辑分离

url2-koa/
|
+- .vscode/
|  |
|  +- launch.json <-- VSCode 配置文件
|
+- controllers/
|  |
|  +- login.js <-- 处理login相关URL
|  |
|  +- users.js <-- 处理用户管理相关URL
|
+- app.js <-- 使用koa的js
|
+- package.json <-- 项目描述文件
|
+- node_modules/ <-- npm安装的所有依赖包
//处理url的文件
var fn_hello = async (ctx, next) => {
    var name = ctx.params.name;
    ctx.response.body = `

Hello, ${name}!

`; }; module.exports = { 'GET /hello/:name': fn_hello };
// 先导入fs模块,然后用readdirSync列出文件
// 这里可以用sync是因为启动时只运行一次,不存在性能问题:
const fs = require('fs');
var files = fs.readdirSync(__dirname + '/controllers');
function addMapping(router, mapping) {
    for (var url in mapping) {
        if (url.startsWith('GET ')) {
            var path = url.substring(4);
            router.get(path, mapping[url]);
            console.log(`register URL mapping: GET ${path}`);
        } else if (url.startsWith('POST ')) {
            var path = url.substring(5);
            router.post(path, mapping[url]);
            console.log(`register URL mapping: POST ${path}`);
        } else {
            console.log(`invalid URL: ${url}`);
        }
    }
}

function addControllers(router) {
    var files = fs.readdirSync(__dirname + '/controllers');
    var js_files = files.filter((f) => {
        return f.endsWith('.js');
    });

    for (var f of js_files) {
        console.log(`process controller: ${f}...`);
        let mapping = require(__dirname + '/controllers/' + f);
        addMapping(router, mapping);
    }
}

addControllers(router);

以上代码又可以封装成一个js,暴露出一个函数接口以供调用和设置扫描路径

4.2 Nunjucks模板引擎

主要用于后端的模板解决方法

需要导入

"nunjucks": "2.4.2"

编写好需要用的render函数

//这个也可以封装好以供调用
const nunjucks = require('nunjucks');

function createEnv(path, opts) {
    var
        autoescape = opts.autoescape === undefined ? true : opts.autoescape,
        noCache = opts.noCache || false,
        watch = opts.watch || false,
        throwOnUndefined = opts.throwOnUndefined || false,
        env = new nunjucks.Environment(
            new nunjucks.FileSystemLoader('views', {
                noCache: noCache,
                watch: watch,
            }), {
                autoescape: autoescape,
                throwOnUndefined: throwOnUndefined
            });
    if (opts.filters) {
        for (var f in opts.filters) {
            env.addFilter(f, opts.filters[f]);
        }
    }
    return env;
}

var env = createEnv('views', {
    watch: true,
    filters: {
        hex: function (n) {
            return '0x' + n.toString(16);
        }
    }
});

然后通过以下来渲染模板

//

Hello {{ name }}

var s = env.render('hello.html', { name: '小明' });//分别传入view和model对象 console.log(s);

同时可以避免恶意脚本如在插入的数据内放script标签之类的

也可以执行一些逻辑



    

Fruits List

{% for f in fruits %}

{{ f }}

{% endfor %}

Nunjucks的模板可继承,先定义一个基本的网页框架base.html


{% block header %} 

Unnamed

{% endblock %}//中间的是模板,可以被替代渲染 {% block body %}
No body
{% endblock %} {% block footer %}
copyright
{% endblock %}

base.html定义了三个可编辑的块,分别命名为headerbodyfooter。子模板可以有选择地对块进行重新定义:

{% extends 'base.html' %}

{% block header %}

{{ header }}

{% endblock %} {% block body %}

{{ body }}

{% endblock %}

4.3 MVC

Model-View-Controller,中文名“模型-视图-控制器”。

处理静态文件

const path = require('path');
const mime = require('mime');
const fs = require('mz/fs');
//mz提供的API和Node.js的fs模块完全相同,但fs模块使用回调,而mz封装了fs对应的函数,并改为Promise
// url: 类似 '/static/'
// dir: 类似 __dirname + '/static'
function staticFiles(url, dir) {
    return async (ctx, next) => {
        let rpath = ctx.request.path;
        // 判断是否以指定的url开头:
        if (rpath.startsWith(url)) {
            // 获取文件完整路径:
            let fp = path.join(dir, rpath.substring(url.length));
            // 判断文件是否存在:
            if (await fs.exists(fp)) {
                // 查找文件的mime:
                ctx.response.type = mime.lookup(rpath);
                // 读取文件内容并赋值给response.body:
                ctx.response.body = await fs.readFile(fp);
            } else {
                // 文件不存在:
                ctx.response.status = 404;
            }
        } else {
            // 不是指定前缀的URL,继续处理下一个middleware:
            await next();
        }
    };
}

module.exports = staticFiles;

4.4 Mysql

配置数据库

//config.js
var config = {
    database: 'test', // 使用哪个数据库
    username: 'www', // 用户名
    password: 'www', // 口令
    host: 'localhost', // 主机名
    port: 3306 // 端口号,MySQL默认3306
};

module.exports = config;

连接数据库

/*依赖包:
"sequelize": "3.24.1",
"mysql": "2.11.1"*/
const Sequelize = require('sequelize');
const config = require('./config');

var sequelize = new Sequelize(config.database, config.username, config.password, {
    host: config.host,
    dialect: 'mysql',
    pool: {
        max: 5,
        min: 0,
        idle: 30000
    }
});

定义模型建立映射

var Pet = sequelize.define('pet', {
    id: {
        type: Sequelize.STRING(50),
        primaryKey: true
    },
    name: Sequelize.STRING(100),
    gender: Sequelize.BOOLEAN,
    birth: Sequelize.STRING(10),
    createdAt: Sequelize.BIGINT,
    updatedAt: Sequelize.BIGINT,
    version: Sequelize.BIGINT
}, {
        timestamps: false//关闭Sequelize的自动添加timestamp的功能
    });
//增
(async () => {
    var dog = await Pet.create({
        id: 'd-' + now,
        name: 'Odie',
        gender: false,
        birth: '2008-08-08',
        createdAt: now,
        updatedAt: now,
        version: 0
    });
    console.log('created: ' + JSON.stringify(dog));
})();
//删
(async () => {
    var p = await queryFromSomewhere();
    await p.destroy();
})();
//改
(async () => {
    var p = await queryFromSomewhere();
    p.gender = true;
    p.updatedAt = Date.now();
    p.version ++;
    await p.save();
})();
//查
(async () => {
    var pets = await Pet.findAll({
        where: {
            name: 'Gaffey'
        }
    });
    console.log(`find ${pets.length} pets:`);
    for (let p of pets) {
        console.log(JSON.stringify(p));
    }
})();

4.5 Model——统一规范

Model存放的文件夹必须在models内,并且以Model名字命名,例如:Pet.jsUser.js等等。

其次,每个Model必须遵守一套规范:

  1. 统一主键,名称必须是id,类型必须是STRING(50)
  2. 主键可以自己指定,也可以由框架自动生成(如果为null或undefined);
  3. 所有字段默认为NOT NULL,除非显式指定;
  4. 统一timestamp机制,每个Model必须有createdAtupdatedAtversion,分别记录创建时间、修改时间和版本号。其中,createdAtupdatedAtBIGINT存储时间戳,最大的好处是无需处理时区,排序方便。version每次修改时自增。

实现方式:db.js

const Sequelize = require('sequelize');

console.log('init sequelize...');

var sequelize = new Sequelize('dbname', 'username', 'password', {
    host: 'localhost',
    dialect: 'mysql',
    pool: {
        max: 5,
        min: 0,
        idle: 10000
    }
});

const ID_TYPE = Sequelize.STRING(50);

function defineModel(name, attributes) {
    var attrs = {};
    for (let key in attributes) {
        let value = attributes[key];
        if (typeof value === 'object' && value['type']) {
            value.allowNull = value.allowNull || false;
            attrs[key] = value;
        } else {
            attrs[key] = {
                type: value,
                allowNull: false
            };
        }
    }
    attrs.id = {
        type: ID_TYPE,
        primaryKey: true
    };
    attrs.createdAt = {
        type: Sequelize.BIGINT,
        allowNull: false
    };
    attrs.updatedAt = {
        type: Sequelize.BIGINT,
        allowNull: false
    };
    attrs.version = {
        type: Sequelize.BIGINT,
        allowNull: false
    };
    return sequelize.define(name, attrs, {
        tableName: name,
        timestamps: false,
        hooks: {
            beforeValidate: function (obj) {
                let now = Date.now();
                if (obj.isNewRecord) {
                    if (!obj.id) {
                        obj.id = generateId();
                    }
                    obj.createdAt = now;
                    obj.updatedAt = now;
                    obj.version = 0;
                } else {
                    obj.updatedAt = Date.now();
                    obj.version++;
                }
            }
        }
    });
}

更便捷地导入Model

const fs = require('fs');
const db = require('./db');

let files = fs.readdirSync(__dirname + '/models');

let js_files = files.filter((f)=>{
    return f.endsWith('.js');
}, files);

module.exports = {};

for (let f of js_files) {
    console.log(`import model from file ${f}...`);
    let name = f.substring(0, f.length - 3);
    module.exports[name] = require(__dirname + '/models/' + f);
}

module.exports.sync = () => {
    db.sync();
};

数据库的默认、特殊覆写、测试环境等多重配置,根据环境选择数据库

const defaultConfig = './config-default.js';
// 可设定为绝对路径,如 /opt/product/config-override.js
const overrideConfig = './config-override.js';
const testConfig = './config-test.js';

const fs = require('fs');

var config = null;

if (process.env.NODE_ENV === 'test') {
    console.log(`Load ${testConfig}...`);
    config = require(testConfig);
} else {
    console.log(`Load ${defaultConfig}...`);
    config = require(defaultConfig);
    try {
        if (fs.statSync(overrideConfig).isFile()) {
            console.log(`Load ${overrideConfig}...`);
            config = Object.assign(config, require(overrideConfig));
        }
    } catch (err) {
        console.log(`Cannot load ${overrideConfig}.`);
    }
}

module.exports = config;

init-db.js——初始化数据库,扫描Model生成SQL脚本创建表

const model = require('./model.js');
model.sync();

console.log('init db ok.');
process.exit(0);

4.6 最终工程结构

model-sequelize/
|
+- .vscode/
|  |
|  +- launch.json <-- VSCode 配置文件
|
+- models/ <-- 存放所有Model
|  |
|  +- Pet.js <-- Pet
|  |
|  +- User.js <-- User
|
+- config.js <-- 配置文件入口
|
+- config-default.js <-- 默认配置文件
|
+- config-test.js <-- 测试配置文件
|
+- db.js <-- 如何定义Model
|
+- model.js <-- 如何导入Model
|
+- init-db.js <-- 初始化数据库
|
+- app.js <-- 业务代码
|
+- package.json <-- 项目描述文件
|
+- node_modules/ <-- npm安装的所有依赖包

4.7 mocha——测试

导包:

 "devDependencies": {//开发环境包,不会在正式安装的时候安装.
    "mocha": "3.0.2"
  }

测试目录

hello-test/
|
+- .vscode/
|  |
|  +- launch.json <-- VSCode 配置文件
|
+- hello.js <-- 待测试js文件
|
+- test/ <-- 存放所有test
| |
|  +- hello-test.js <-- 测试文件
|
+- package.json <-- 项目描述文件
|
+- node_modules/ <-- npm安装的所有依赖包
// hello.js待测试文件

module.exports = function (...rest) {
    var sum = 0;
    for (let n of rest) {
        sum += n;
    }
    return sum;
};
//hello-test.js测试文件
const assert = require('assert');

const sum = require('../hello');

describe('#hello.js', () => {

    describe('#sum()', () => {
         before(function () {//测试前
            console.log('before:');
        });

        after(function () {//测试后
            console.log('after.');
        });

        beforeEach(function () {//每次测试前
            console.log('  beforeEach:');
        });

        afterEach(function () {//每次测试后
            console.log('  afterEach.');
        });
        it('sum() should return 0', () => {
            assert.strictEqual(sum(), 0);
        });

        it('sum(1) should return 1', () => {
            assert.strictEqual(sum(1), 1);
        });

        it('sum(1, 2) should return 3', () => {
            assert.strictEqual(sum(1, 2), 3);
        });

        it('sum(1, 2, 3) should return 6', () => {
            assert.strictEqual(sum(1, 2, 3), 6);
        });
    });
});

测试异步函数

it('#async function', async () => {
    let r = await hello();
    assert.strictEqual(r, 15);
});

也可以跑http测试

const
    request = require('supertest'),
    app = require('../app');

describe('#test koa app', () => {

    let server = app.listen(9900);//测试环境下的启动端口

    describe('#test server', () => {

        it('#test GET /', async () => {
            let res = await request(server)
                .get('/')
                .expect('Content-Type', /text\/html/)
                .expect(200, '

Hello, world!

'); }); it('#test GET /path?name=Bob', async () => { let res = await request(server) .get('/path?name=Bob') .expect('Content-Type', /text\/html/) .expect(200, '

Hello, Bob!

'); }); }); });

但个人觉得不如直接用api测试工具

4.8 WebSocket

导包

"dependencies": {
    "ws": "1.1.1"
}
// 导入WebSocket模块:
const WebSocket = require('ws');

// 引用Server类:
const WebSocketServer = WebSocket.Server;

// 实例化:
const wss = new WebSocketServer({
    port: 3000
});
wss.on('connection', function (ws) {
    console.log(`[SERVER] connection()`);
    ws.on('message', function (message) {
        console.log(`[SERVER] Received: ${message}`);
        ws.send(`ECHO: ${message}`, (err) => {
            if (err) {
                console.log(`[SERVER] error: ${err}`);
            }
        });
    })
});

4.8 REST API——JSON

返回JSON格式的响应的api

示例:

var products = [{
    name: 'iPhone',
    price: 6999
}, {
    name: 'Kindle',
    price: 999
}];

module.exports = {
    'GET /api/products': async (ctx, next) => {
        ...
    },

    'POST /api/products': async (ctx, next) => {
        var p = {
            name: ctx.request.body.name,
            price: ctx.request.body.price
        };
        products.push(p);
        ctx.response.type = 'application/json';
        ctx.response.body = p;
    }
};

评论