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暴露了module
和exports
两个变量,其中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——加密算法
为了让运行速度更快,nodejs
用c/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请求
nodejs
和koa
提供的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
定义了三个可编辑的块,分别命名为header
、body
和footer
。子模板可以有选择地对块进行重新定义:
{% 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.js
,User.js
等等。
其次,每个Model必须遵守一套规范:
- 统一主键,名称必须是
id
,类型必须是STRING(50)
; - 主键可以自己指定,也可以由框架自动生成(如果为null或undefined);
- 所有字段默认为
NOT NULL
,除非显式指定; - 统一timestamp机制,每个Model必须有
createdAt
、updatedAt
和version
,分别记录创建时间、修改时间和版本号。其中,createdAt
和updatedAt
以BIGINT
存储时间戳,最大的好处是无需处理时区,排序方便。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;
}
};