最近讀《重學前端》,開篇就是讓你擁有本身的知識體系圖譜,後續學的東西補充到相應的模塊,既能夠加深對原有知識的理解,又能夠強化記憶,很不錯的學習方案。javascript
這篇文章主要知識點來自:css
比較好的 markdown 的查看方式是直接用 VSCode 打開大綱,這樣整個脈絡一目瞭然,後續補充知識點也很快定位到相應的位置:html
這個 markdown 文件已經丟到 Github,有更新會直接推這裏:前端
博客上也會記錄一些好玩的東西:node
# 使用 nvm 安裝
https://github.com/creationix/nvm#install-script # Git install
nvm install
nvm alias default
# 卸載 pkg 安裝版
sudo rm -rf /usr/local/{bin/{node,npm},lib/node_modules/npm,lib/node,share/man/*/node.*}
複製代碼
// 經過以下模塊包裝獲得
(funciton (exports, require, module, __filename, __dirname) { // 包裝頭
}); // 包裝尾
複製代碼
fs.readFileSync()
加載JSON.parse()
解析(funciton(exports, require, module, __filename, __dirname) { // 包裝頭
console.log('hello world!') // 原始文件
}); // 包裝尾
複製代碼
// module-2.js
exports.method = function() {
return 'Hello';
};
exports.method2 = function() {
return 'Hello again';
};
// module-1.js
const module2 = require('./module-2');
console.log(module2.method()); // Hello
console.log(module2.method2()); // Hello again
複製代碼
console.log('__dirname:', __dirname); // 文件夾
console.log('__filename:', __filename); // 文件
path.join(__dirname, 'views', 'view.html'); // 若是不但願本身手動處理 / 的問題,使用 path.join
複製代碼
佔位符 | 類型 | 例子 |
---|---|---|
%s | String | console.log('%s', 'value') |
%d | Number | console.log('%d', 3.14) |
%j | JSON | console.log('%j', {name: 'Chenng'}) |
node
console.log(process.env.PATH.split(':').join('\n'));
複製代碼
process.env.PATH += ':/a_new_path_to_executables';
複製代碼
// 獲取平臺信息
process.arch // x64
process.platform // darwin
// 獲取內存使用狀況
process.memoryUsage();
// 獲取命令行參數
process.argv
複製代碼
process.nextTick 方法容許你把一個回調放在下一次時間輪詢隊列的頭上,這意味着能夠用來延遲執行,結果是比 setTimeout 更有效率。nginx
const EventEmitter = require('events').EventEmitter;
function complexOperations() {
const events = new EventEmitter();
process.nextTick(function () {
events.emit('success');
});
return events;
}
complexOperations().on('success', function () {
console.log('success!');
});
複製代碼
若是沒有提供編碼格式,文件操做以及不少網絡操做就會將數據做爲 Buffer 類型返回。git
默認轉爲 UTF-8
格式,還支持 ascii
、base64
等。github
// 生成 data URI
const fs = require('fs');
const mime = 'image/png';
const encoding = 'base64';
const base64Data = fs.readFileSync(`${__dirname}/monkey.png`).toString(encoding);
const uri = `data:${mime};${encoding},${base64Data}`;
console.log(uri);
// data URI 轉文件
const fs = require('fs');
const uri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...';
const base64Data = uri.split(',')[1];
const buf = Buffer(base64Data, 'base64');
fs.writeFileSync(`${__dirname}/secondmonkey.png`, buf);
複製代碼
const EventEmitter = require('events').EventEmitter;
const AudioDevice = {
play: function (track) {
console.log('play', track);
},
stop: function () {
console.log('stop');
},
};
class MusicPlayer extends EventEmitter {
constructor() {
super();
this.playing = false;
}
}
const musicPlayer = new MusicPlayer();
musicPlayer.on('play', function (track) {
this.playing = true;
AudioDevice.play(track);
});
musicPlayer.on('stop', function () {
this.playing = false;
AudioDevice.stop();
});
musicPlayer.emit('play', 'The Roots - The Fire');
setTimeout(function () {
musicPlayer.emit('stop');
}, 1000);
// 處理異常
// EventEmitter 實例發生錯誤會發出一個 error 事件
// 若是沒有監聽器,默認動做是打印一個堆棧並退出程序
musicPlayer.on('error', function (err) {
console.err('Error:', err);
});
複製代碼
const util = require('util');
const fs = require('fs');
const readAsync = util.promisify(fs.readFile);
async function init() {
try {
let data = await readAsync('./package.json');
data =JSON.parse(data);
console.log(data.name);
} catch (err) {
console.log(err);
}
}
複製代碼
流是基於事件的 API,用於管理和處理數據。
理解流的最好方式就是想象一下沒有流的時候怎麼處理數據:
fs.readFileSync
同步讀取文件,程序會阻塞,全部數據被讀到內存fs.readFile
阻止程序阻塞,但仍會將文件全部數據讀取到內存中fs.createReadStream
想要經過網絡高效且支持大文件的發送一個文件到一個客戶端。
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
fs.readFile(`${__dirname}/index.html`, (err, data) => {
if (err) {
res.statusCode = 500;
res.end(String(err));
return;
}
res.end(data);
});
}).listen(8000);
複製代碼
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
fs.createReadStream(`${__dirname}/index.html`).pipe(res);
}).listen(8000);
複製代碼
const http = require('http');
const fs = require('fs');
const zlib = require('zlib');
http.createServer((req, res) => {
res.writeHead(200, {
'content-encoding': 'gzip',
});
fs.createReadStream(`${__dirname}/index.html`)
.pipe(zlib.createGzip())
.pipe(res);
}).listen(8000);
複製代碼
const fs = require('fs');
const stream = fs.createReadStream('not-found');
stream.on('error', (err) => {
console.trace();
console.error('Stack:', err.stack);
console.error('The error raised was:', err);
});
複製代碼
可讀流被用來爲 I/O 源提供靈活的 API,也能夠被用做解析器:
_read(size)
方法json-lines.txt
{ "position": 0, "letter": "a" }
{ "position": 1, "letter": "b" }
{ "position": 2, "letter": "c" }
{ "position": 3, "letter": "d" }
{ "position": 4, "letter": "e" }
{ "position": 5, "letter": "f" }
{ "position": 6, "letter": "g" }
{ "position": 7, "letter": "h" }
{ "position": 8, "letter": "i" }
{ "position": 9, "letter": "j" }
複製代碼
JSONLineReader.js
const stream = require('stream');
const fs = require('fs');
const util = require('util');
class JSONLineReader extends stream.Readable {
constructor(source) {
super();
this._source = source;
this._foundLineEnd = false;
this._buffer = '';
source.on('readable', () => {
this.read();
});
}
// 全部定製 stream.Readable 類都須要實現 _read 方法
_read(size) {
let chunk;
let line;
let result;
if (this._buffer.length === 0) {
chunk = this._source.read();
this._buffer += chunk;
}
const lineIndex = this._buffer.indexOf('\n');
if (lineIndex !== -1) {
line = this._buffer.slice(0, lineIndex); // 從 buffer 的開始截取第一行來獲取一些文本進行解析
if (line) {
result = JSON.parse(line);
this._buffer = this._buffer.slice(lineIndex + 1);
this.emit('object', result); // 當一個 JSON 記錄解析出來的時候,觸發一個 object 事件
this.push(util.inspect(result)); // 將解析好的 SJON 發回內部隊列
} else {
this._buffer = this._buffer.slice(1);
}
}
}
}
const input = fs.createReadStream(`${__dirname}/json-lines.txt`, {
encoding: 'utf8',
});
const jsonLineReader = new JSONLineReader(input); // 建立一個 JSONLineReader 實例,傳遞一個文件流給它處理
jsonLineReader.on('object', (obj) => {
console.log('pos:', obj.position, '- letter:', obj.letter);
});
複製代碼
可寫的流可用於輸出數據到底層 I/O:
stream.Writable
_write
方法向底層源數據發送數據cat json-lines.txt | node stram_writable.js
複製代碼
stram_writable.js
const stream = require('stream');
class GreenStream extends stream.Writable {
constructor(options) {
super(options);
}
_write(chunk, encoding, cb) {
process.stdout.write(`\u001b[32m${chunk}\u001b[39m`);
cb();
}
}
process.stdin.pipe(new GreenStream());
複製代碼
雙工流容許發送和接受數據:
stream.Duplex
_read
和 _write
方法使用流改變數據爲另外一種格式,而且高效地管理內存:
stream.Transform
_transform
方法使用 Node 內置的斷言模塊測試
const assert = require('assert');
const fs = require('fs');
const CSVParser = require('./csvparser');
const parser = new CSVParser();
const actual = [];
fs.createReadStream(`${__dirname}/sample.csv`)
.pipe(parser);
process.on('exit', function () {
actual.push(parser.read());
actual.push(parser.read());
actual.push(parser.read());
const expected = [
{ name: 'Alex', location: 'UK', role: 'admin' },
{ name: 'Sam', location: 'France', role: 'user' },
{ name: 'John', location: 'Canada', role: 'user' },
];
assert.deepEqual(expected, actual);
});
複製代碼
fs 方法 | 描述 |
---|---|
fs.truncate | 截斷或者拓展文件到制定的長度 |
fs.ftruncate | 和 truncate 同樣,但將文件描述符做爲參數 |
fs.chown | 改變文件的全部者以及組 |
fs.fchown | 和 chown 同樣,但將文件描述符做爲參數 |
fs.lchown | 和 chown 同樣,但不解析符號連接 |
fs.stat | 獲取文件狀態 |
fs.lstat | 和 stat 同樣,可是返回信息是關於符號連接而不是它指向的內容 |
fs.fstat | 和 stat 同樣,但將文件描述符做爲參數 |
fs.link | 建立一個硬連接 |
fs.symlink | 建立一個軟鏈接 |
fs.readlink | 讀取一個軟鏈接的值 |
fs.realpath | 返回規範的絕對路徑名 |
fs.unlink | 刪除文件 |
fs.rmdir | 刪除文件目錄 |
fs.mkdir | 建立文件目錄 |
fs.readdir | 讀取一個文件目錄的內容 |
fs.close | 關閉一個文件描述符 |
fs.open | 打開或者建立一個文件用來讀取或者寫入 |
fs.utimes | 設置文件的讀取和修改時間 |
fs.futimes | 和 utimes 同樣,但將文件描述符做爲參數 |
fs.fsync | 同步磁盤中的文件數據 |
fs.write | 寫入數據到一個文件 |
fs.read | 讀取一個文件的數據 |
const fs = require('fs');
const assert = require('assert');
const fd = fs.openSync('./file.txt', 'w+');
const writeBuf = new Buffer('some data to write');
fs.writeSync(fd, writeBuf, 0, writeBuf.length, 0);
const readBuf = new Buffer(writeBuf.length);
fs.readSync(fd, readBuf, 0, writeBuf.length, 0);
assert.equal(writeBuf.toString(), readBuf.toString());
fs.closeSync(fd);
複製代碼
const fs = require('fs');
const readable = fs.createReadStream('./original.txt');
const writeable = fs.createWriteStream('./copy.txt');
readable.pipe(writeable);
複製代碼
fs.watchFile
比 fs.watch
低效,但更好用。
同步 fs 的方法應該在第一次初始化應用的時候使用。
const fs = require('fs');
const config = JSON.parse(fs.readFileSync('./config.json').toString());
init(config);
複製代碼
require:
const config = require('./config.json);
init(config);
複製代碼
Object.freeze
來凍結一個對象文件描述是在操做系統中管理的在進程中打開文件所關聯的一些數字或者索引。操做系統經過指派一個惟一的整數給每一個打開的文件用來查看關於這個文件
Stream | 文件描述 | 描述 |
---|---|---|
stdin | 0 | 標準輸入 |
stdout | 1 | 標準輸出 |
stderr | 2 | 標準錯誤 |
console.log('Log')
是 process.stdout.write('log')
的語法糖。
一個文件描述是 open 以及 openSync 方法調用返回的一個數字
const fd = fs.openSync('myfile', 'a');
console.log(typeof fd === 'number'); // true
複製代碼
協同多個進程同時訪問一個文件,保證文件的完整性以及數據不能丟失:
node-fs-ext
經過 flock
鎖住一個文件Node 實現鎖文件
// 全部須要打開文件的方法,fs.writeFile、fs.createWriteStream、fs.open 都有一個 x 標記
// 這個文件應該已獨佔打開,若這個文件存在,文件不能被打開
fs.open('config.lock', 'wx', (err) => {
if (err) { return console.err(err); }
});
// 最好將當前進程號寫進文件鎖中
// 當有異常的時候就知道最後這個鎖的進程
fs.writeFile(
'config.lock',
process.pid,
{ flogs: 'wx' },
(err) => {
if (err) { return console.error(err) };
},
);
複製代碼
獨佔標記有個問題,可能有些系統不能識別 0_EXCL
標記。另外一個方案是把鎖文件換成一個目錄,PID 能夠寫入目錄中的一個文件。
fs.mkidr('config.lock', (err) => {
if (err) { return console.error(err); }
fs.writeFile(`/config.lock/${process.pid}`, (err) => {
if (err) { return console.error(err); }
});
});
複製代碼
const fs = require('fs');
const lockDir = 'config.lock';
let hasLock = false;
exports.lock = function (cb) { // 獲取鎖
if (hasLock) { return cb(); } // 已經獲取了一個鎖
fs.mkdir(lockDir, function (err) {
if (err) { return cb(err); } // 沒法建立鎖
fs.writeFile(lockDir + '/' + process.pid, function (err) { // 把 PID寫入到目錄中以便調試
if (err) { console.error(err); } // 沒法寫入 PID,繼續運行
hasLock = true; // 鎖建立了
return cb();
});
});
};
exports.unlock = function (cb) { // 解鎖方法
if (!hasLock) { return cb(); } // 若是沒有須要解開的鎖
fs.unlink(lockDir + '/' + process.pid, function (err) {
if (err) { return cb(err); }
fs.rmdir(lockDir, function (err) {
if (err) return cb(err);
hasLock = false;
cb();
});
});
};
process.on('exit', function () {
if (hasLock) {
fs.unlinkSync(lockDir + '/' + process.pid); // 若是還有鎖,在退出以前同步刪除掉
fs.rmdirSync(lockDir);
console.log('removed lock');
}
});
複製代碼
一個線上庫:mkdirp
遞歸:要解決咱們的問題就要先解決更小的相同的問題。
dir-a
├── dir-b
│ ├── dir-c
│ │ ├── dir-d
│ │ │ └── file-e.png
│ │ └── file-e.png
│ ├── file-c.js
│ └── file-d.txt
├── file-a.js
└── file-b.txt
複製代碼
查找模塊:find /asset/dir-a -name="file.*"
[
'dir-a/dir-b/dir-c/dir-d/file-e.png',
'dir-a/dir-b/dir-c/file-e.png',
'dir-a/dir-b/file-c.js',
'dir-a/dir-b/file-d.txt',
'dir-a/file-a.js',
'dir-a/file-b.txt',
]
複製代碼
const fs = require('fs');
const join = require('path').join;
// 同步查找
exports.findSync = function (nameRe, startPath) {
const results = [];
function finder(path) {
const files = fs.readdirSync(path);
for (let i = 0; i < files.length; i++) {
const fpath = join(path, files[i]);
const stats = fs.statSync(fpath);
if (stats.isDirectory()) { finder(fpath); }
if (stats.isFile() && nameRe.test(files[i])) {
results.push(fpath);
}
}
}
finder(startPath);
return results;
};
// 異步查找
exports.find = function (nameRe, startPath, cb) { // cb 能夠傳入 console.log,靈活
const results = [];
let asyncOps = 0; // 2
function finder(path) {
asyncOps++;
fs.readdir(path, function (er, files) {
if (er) { return cb(er); }
files.forEach(function (file) {
const fpath = join(path, file);
asyncOps++;
fs.stat(fpath, function (er, stats) {
if (er) { return cb(er); }
if (stats.isDirectory()) finder(fpath);
if (stats.isFile() && nameRe.test(file)) {
results.push(fpath);
}
asyncOps--;
if (asyncOps == 0) {
cb(null, results);
}
});
});
asyncOps--;
if (asyncOps == 0) {
cb(null, results);
}
});
}
finder(startPath);
};
console.log(exports.findSync(/file.*/, `${__dirname}/dir-a`));
console.log(exports.find(/file.*/, `${__dirname}/dir-a`, console.log));
複製代碼
想要監聽一個文件或者目錄,並在文件更改後執行一個動做。
const fs = require('fs');
fs.watch('./watchdir', console.log); // 穩定且快
fs.watchFile('./watchdir', console.log); // 跨平臺
複製代碼
const fs = require('fs');
const readline = require('readline');
const rl = readline.createInterface({
input: fs.createReadStream('/etc/hosts'),
crlfDelay: Infinity
});
rl.on('line', (line) => {
console.log(`cc ${line}`);
const extract = line.match(/(\d+\.\d+\.\d+\.\d+) (.*)/);
});
複製代碼
function get_local_ip() {
const interfaces = require('os').networkInterfaces();
let IPAdress = '';
for (const devName in interfaces) {
const iface = interfaces[devName];
for (let i = 0; i < iface.length; i++) {
const alias = iface[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
IPAdress = alias.address;
}
}
}
return IPAdress;
}
複製代碼
NodeJS 使用 net
模塊建立 TCP 鏈接和服務。
const assert = require('assert');
const net = require('net');
let clients = 0;
let expectedAssertions = 2;
const server = net.createServer(function (client) {
clients++;
const clientId = clients;
console.log('Client connected:', clientId);
client.on('end', function () {
console.log('Client disconnected:', clientId);
});
client.write('Welcome client: ' + clientId);
client.pipe(client);
});
server.listen(8000, function () {
console.log('Server started on port 8000');
runTest(1, function () {
runTest(2, function () {
console.log('Tests finished');
assert.equal(0, expectedAssertions);
server.close();
});
});
});
function runTest(expectedId, done) {
const client = net.connect(8000);
client.on('data', function (data) {
const expected = 'Welcome client: ' + expectedId;
assert.equal(data.toString(), expected);
expectedAssertions--;
client.end();
});
client.on('end', done);
}
複製代碼
利用 dgram
模塊建立數據報 socket
,而後利用 socket.send
發送數據。
const dgram = require('dgram');
const fs = require('fs');
const port = 41230;
const defaultSize = 16;
function Client(remoteIP) {
const inStream = fs.createReadStream(__filename); // 從當前文件建立可讀流
const socket = dgram.createSocket('udp4'); // 建立新的數據流 socket 做爲客戶端
inStream.on('readable', function () {
sendData(); // 當可讀流準備好,開始發送數據到服務器
});
function sendData() {
const message = inStream.read(defaultSize); // 讀取數據塊
if (!message) {
return socket.unref(); // 客戶端完成任務後,使用 unref 安全關閉它
}
// 發送數據到服務器
socket.send(message, 0, message.length, port, remoteIP, function () {
sendData();
}
);
}
}
function Server() {
const socket = dgram.createSocket('udp4'); // 建立一個 socket 提供服務
socket.on('message', function (msg) {
process.stdout.write(msg.toString());
});
socket.on('listening', function () {
console.log('Server ready:', socket.address());
});
socket.bind(port);
}
if (process.argv[2] === 'client') { // 根據命令行選項肯定運行客戶端仍是服務端
new Client(process.argv[3]);
} else {
new Server();
}
複製代碼
使用 http.createServer
和 http.createClient
運行 HTTP 服務。
const assert = require('assert');
const http = require('http');
const server = http.createServer(function(req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' }); // 寫入基於文本的響應頭
res.write('Hello, world.'); // 發送消息回客戶端
res.end();
});
server.listen(8000, function() {
console.log('Listening on port 8000');
});
const req = http.request({ port: 8000}, function(res) { // 建立請求
console.log('HTTP headers:', res.headers);
res.on('data', function(data) { // 給 data 事件建立監聽,確保和指望值一致
console.log('Body:', data.toString());
assert.equal('Hello, world.', data.toString());
assert.equal(200, res.statusCode);
server.unref();
console.log('測試完成');
});
});
req.end();
複製代碼
HTTP 標準定義了標識重定向發生時的狀態碼,它也指出了客戶端應該檢查無限循環。
const http = require('http');
const https = require('https');
const url = require('url'); // 有不少接續 URLs 的方法
// 構造函數被用來建立一個對象來構成請求對象的聲明週期
function Request() {
this.maxRedirects = 10;
this.redirects = 0;
}
Request.prototype.get = function(href, callback) {
const uri = url.parse(href); // 解析 URLs 成爲 Node http 模塊使用的格式,肯定是否使用 HTTPS
const options = { host: uri.host, path: uri.path };
const httpGet = uri.protocol === 'http:' ? http.get : https.get;
console.log('GET:', href);
function processResponse(response) {
if (response.statusCode >= 300 && response.statusCode < 400) { // 檢查狀態碼是否在 HTTP 重定向範圍
if (this.redirects >= this.maxRedirects) {
this.error = new Error('Too many redirects for: ' + href);
} else {
this.redirects++; // 重定向計數自增
href = url.resolve(options.host, response.headers.location); // 使用 url.resolve 確保相對路徑的 URLs 轉換爲絕對路徑 URLs
return this.get(href, callback);
}
}
response.url = href;
response.redirects = this.redirects;
console.log('Redirected:', href);
function end() {
console.log('Connection ended');
callback(this.error, response);
}
response.on('data', function(data) {
console.log('Got data, length:', data.length);
});
response.on('end', end.bind(this)); // 綁定回調到 Request 實例,確保能拿到實例屬性
}
httpGet(options, processResponse.bind(this))
.on('error', function(err) {
callback(err);
});
};
const request = new Request();
request.get('http://google.com/', function(err, res) {
if (err) {
console.error(err);
} else {
console.log(` Fetched URL: ${res.url} with ${res.redirects} redirects `);
process.exit();
}
});
複製代碼
const http = require('http');
const url = require('url');
http.createServer(function(req, res) {
console.log('start request:', req.url);
const options = url.parse(req.url);
console.log(options);
options.headers = req.headers;
const proxyRequest = http.request(options, function(proxyResponse) { // 建立請求來複制原始的請求
proxyResponse.on('data', function(chunk) { // 監聽數據,返回給瀏覽器
console.log('proxyResponse length:', chunk.length);
res.write(chunk, 'binary');
});
proxyResponse.on('end', function() { // 追蹤代理請求完成
console.log('proxied request ended');
res.end();
});
res.writeHead(proxyResponse.statusCode, proxyResponse.headers); // 發送頭部信息給服務器
});
req.on('data', function(chunk) { // 捕獲從瀏覽器發送到服務器的數據
console.log('in request length:', chunk.length);
proxyRequest.write(chunk, 'binary');
});
req.on('end', function() { // 追蹤原始的請求何時結束
console.log('original request ended');
proxyRequest.end();
});
}).listen(8888); // 監聽來自本地瀏覽器的鏈接
複製代碼
const https = require('https');
const promisify = require('util').promisify;
https.get[promisify.custom] = function getAsync(options) {
return new Promise((resolve, reject) => {
https.get(options, (response) => {
response.end = new Promise((resolve) => response.on('end', resolve));
resolve(response);
}).on('error', reject);
});
};
const rp = promisify(https.get);
(async () => {
const res = await rp('https://jsonmock.hackerrank.com/api/movies/search/?Title=Spiderman&page=1');
let body = '';
res.on('data', (chunk) => body += chunk);
await res.end;
console.log(body);
})();
複製代碼
使用 dns
模塊建立 DNS 請求。
dns.resolve
,A 記錄存儲 IP 地址dns.resulveTxt
,文本值能夠用於在 DNS 上構建其餘服務dns.resolveSrv
,服務記錄定義服務的定位數據,一般包含主機名和端口號dns.resolveNs
,指定域名服務器dns.resolveCname
,相關的域名記錄,設置爲域名而不是 IP 地址const dns = require('dns');
dns.resolve('www.chenng.cn', function (err, addresses) {
if (err) {
console.error(err);
}
console.log('Addresses:', addresses);
});
複製代碼
const crypto = require('crypto')
function aesEncrypt(data, key = 'key') {
const cipher = crypto.createCipher('aes192', key)
let crypted = cipher.update(data, 'utf8', 'hex')
crypted += cipher.final('hex')
return crypted
}
function aesDecrypt(encrypted, key = 'key') {
const decipher = crypto.createDecipher('aes192', key)
let decrypted = decipher.update(encrypted, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
}
複製代碼
request-promise
const cp = require('child_process');
cp.execFile('echo', ['hello', 'world'], (err, stdout, stderr) => {
if (err) { console.error(err); }
console.log('stdout: ', stdout);
console.log('stderr: ', stderr);
});
複製代碼
const cp = require('child_process');
const child = cp.spawn('echo', ['hello', 'world']);
child.on('error', console.error);
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);
複製代碼
const cp = require('child_process');
const path = require('path');
const cat = cp.spawn('cat', [path.resolve(__dirname, 'messy.txt')]);
const sort = cp.spawn('sort');
const uniq = cp.spawn('uniq');
cat.stdout.pipe(sort.stdin);
sort.stdout.pipe(uniq.stdin);
uniq.stdout.pipe(process.stdout);
複製代碼
const cp = require('child_process');
cp.exec(`cat ${__dirname}/messy.txt | sort | uniq`, (err, stdout, stderr) => {
console.log(stdout);
});
複製代碼
process.on('message')
、process.send()
child.on('message')
、child.send()
// parent.js
const cp = require('child_process');
const child = cp.fork('./child', { silent: true });
child.send('monkeys');
child.on('message', function (message) {
console.log('got message from child', message, typeof message);
})
child.stdout.pipe(process.stdout);
setTimeout(function () {
child.disconnect();
}, 3000);
複製代碼
// child.js
process.on('message', function (message) {
console.log('got one', message);
process.send('no pizza');
process.send(1);
process.send({ my: 'object' });
process.send(false);
process.send(null);
});
console.log(process);
複製代碼
const spawn = require('child_process').spawn;
const children = [];
process.on('exit', function () {
console.log('killing', children.length, 'child processes');
children.forEach(function (child) {
child.kill();
});
});
children.push(spawn('/bin/sleep', ['10']));
children.push(spawn('/bin/sleep', ['10']));
children.push(spawn('/bin/sleep', ['10']));
setTimeout(function () { process.exit(0); }, 3000);
複製代碼
TCP
、UDP
等process.env.UV_THREADPOOL_SIZE = 64
複製代碼
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`主進程 ${process.pid} 正在運行`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`工做進程 ${worker.process.pid} 已退出`);
});
} else {
// 工做進程能夠共享任何 TCP 鏈接。
// 在本例子中,共享的是 HTTP 服務器。
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello World');
}).listen(8000);
console.log(`工做進程 ${process.pid} 已啓動`);
}
複製代碼
const {
isMainThread,
parentPort,
workerData,
threadId,
MessageChannel,
MessagePort,
Worker
} = require('worker_threads');
function mainThread() {
for (let i = 0; i < 5; i++) {
const worker = new Worker(__filename, { workerData: i });
worker.on('exit', code => { console.log(`main: worker stopped with exit code ${code}`); });
worker.on('message', msg => {
console.log(`main: receive ${msg}`);
worker.postMessage(msg + 1);
});
}
}
function workerThread() {
console.log(`worker: workerDate ${workerData}`);
parentPort.on('message', msg => {
console.log(`worker: receive ${msg}`);
}),
parentPort.postMessage(workerData);
}
if (isMainThread) {
mainThread();
} else {
workerThread();
}
複製代碼
const assert = require('assert');
const {
Worker,
MessageChannel,
MessagePort,
isMainThread,
parentPort
} = require('worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
const subChannel = new MessageChannel();
worker.postMessage({ hereIsYourPort: subChannel.port1 }, [subChannel.port1]);
subChannel.port2.on('message', (value) => {
console.log('received:', value);
});
} else {
parentPort.once('message', (value) => {
assert(value.hereIsYourPort instanceof MessagePort);
value.hereIsYourPort.postMessage('the worker is sending this');
value.hereIsYourPort.close();
});
}
複製代碼
進程是資源分配的最小單位,線程是CPU調度的最小單位
process.on('uncaughtException', (error) => {
// 我剛收到一個從未被處理的錯誤
// 如今處理它,並決定是否須要重啓應用
errorManagement.handler.handleError(error);
if (!errorManagement.handler.isTrustedError(error)) {
process.exit(1);
}
});
process.on('unhandledRejection', (reason, p) => {
// 我剛剛捕獲了一個未處理的promise rejection,
// 由於咱們已經有了對於未處理錯誤的後備的處理機制(見下面)
// 直接拋出,讓它來處理
throw reason;
});
複製代碼
const domain = require('domain');
const audioDomain = domain.create();
audioDomain.on('error', function(err) {
console.log('audioDomain error:', err);
});
audioDomain.run(function() {
const musicPlayer = new MusicPlayer();
musicPlayer.play();
});
複製代碼
const memberSchema = Joi.object().keys({
password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
birthyear: Joi.number().integer().min(1900).max(2013),
email: Joi.string().email(),
});
function addNewMember(newMember) {
//assertions come first
Joi.assert(newMember, memberSchema); //throws if validation fails
//other logic here
}
複製代碼
var winston = require('winston');
var moment = require('moment');
const logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({
timestamp: function() {
return moment().format('YYYY-MM-DD HH:mm:ss')
},
formatter: function(params) {
let time = params.timestamp() // 時間
let message = params.message // 手動信息
let meta = params.meta && Object.keys(params.meta).length ? '\n\t'+ JSON.stringify(params.meta) : ''
return `${time} ${message}`
},
}),
new (winston.transports.File)({
filename: `${__dirname}/../winston/winston.log`,
json: false,
timestamp: function() {
return moment().format('YYYY-MM-DD HH:mm:ss')
},
formatter: function(params) {
let time = params.timestamp() // 時間
let message = params.message // 手動信息
let meta = params.meta && Object.keys(params.meta).length ? '\n\t'+ JSON.stringify(params.meta) : ''
return `${time} ${message}`
}
})
]
})
module.exports = logger
// logger.error('error')
// logger.warm('warm')
// logger.info('info')
複製代碼
Node 處理 CPU 密集型任務,如 gzipping,SSL termination 等,表現糟糕。相反,使用一個真正的中間件服務像 Nginx 更好。不然可憐的單線程 Node 將不幸地忙於處理網絡任務,而不是處理應用程序核心,性能會相應下降。
雖然 express.js 經過一些 connect 中間件處理靜態文件,但你不該該使用它。Nginx 能夠更好地處理靜態文件,並能夠防止請求動態內容堵塞咱們的 node 進程。
# 配置 gzip 壓縮
gzip on;
gzip_comp_level 6;
gzip_vary on;
# 配置 upstream
upstream myApplication {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
keepalive 64;
}
#定義 web server
server {
# configure server with ssl and error pages
listen 80;
listen 443 ssl;
ssl_certificate /some/location/sillyfacesociety.com.bundle.crt;
error_page 502 /errors/502.html;
# handling static content
location ~ ^/(images/|img/|javascript/|js/|css/|stylesheets/|flash/|media/|static/|robots.txt|humans.txt|favicon.ico) {
root /usr/local/silly_face_society/node/public;
access_log off;
expires max;
}
複製代碼
pm2 start app.js -i 4
,-i 4
是以 cluster_mode 形式運行 app,有 4 個工做線程,若是配置 0
,PM2 會根據 CPU 核心數來生成對應的工做線程pm2 scale <app name> <n>
對集羣進行擴展pm2 save
保存當前運行的應用pm2 startup
啓動使用 ESLint 插件檢測:
{
"extends": [
"plugin:you-dont-need-lodash-underscore/compatible"
]
}
複製代碼
const _ = require('lodash'),
__ = require('underscore'),
Suite = require('benchmark').Suite,
opts = require('./utils');
//cf. https://github.com/Berkmann18/NativeVsUtils/blob/master/utils.js
const concatSuite = new Suite('concat', opts);
const array = [0, 1, 2];
concatSuite.add('lodash', () => _.concat(array, 3, 4, 5))
.add('underscore', () => __.concat(array, 3, 4, 5))
.add('native', () => array.concat(3, 4, 5))
.run({ 'async': true });
複製代碼
node --prof profile-test.js
複製代碼
npm install tick -g
node-tick-processor
複製代碼
yarn add heapdump -D
複製代碼
const heapdump = require('heapdump');
const string = '1 string to rule them all';
const leakyArr = [];
let count = 2;
setInterval(function () {
leakyArr.push(string.replace(/1/g, count++));
}, 0);
setInterval(function () {
if (heapdump.writeSnapshot()) console.log('wrote snapshot');
}, 20000);
複製代碼
檢測頭部配置:Security Headers。
應用程序應該使用安全的 header 來防止攻擊者使用常見的攻擊方式,諸如跨站點腳本攻擊(XSS)、跨站請求僞造(CSRF)。可使用模塊 helmet 輕鬆進行配置。
使用安全檢驗插件 eslint-plugin-security 或者 tslint-config-security。
DOS 攻擊很是流行並且相對容易處理。使用外部服務,好比 cloud 負載均衡, cloud 防火牆, nginx, 或者(對於小的,不是那麼重要的app)一個速率限制中間件(好比 koa-ratelimit),來實現速率限制。
存儲在源代碼管理中的機密信息必須進行加密和管理 (滾動密鑰(rolling keys)、過時時間、審覈等)。使用 pre-commit/push 鉤子防止意外提交機密信息。
要防止 SQL/NoSQL 注入和其餘惡意攻擊, 請始終使用 ORM/ODM 或 database 庫來轉義數據或支持命名的或索引的參數化查詢, 並注意驗證用戶輸入的預期類型。不要只使用 JavaScript 模板字符串或字符串串聯將值插入到查詢語句中, 由於這會將應用程序置於普遍的漏洞中。
庫:
密碼或機密信息(API 密鑰)應該使用安全的 hash + salt 函數(bcrypt)來存儲, 由於性能和安全緣由, 這應該是其 JavaScript 實現的首選。
// 使用10個哈希回合異步生成安全密碼
bcrypt.hash('myPassword', 10, function(err, hash) {
// 在用戶記錄中存儲安全哈希
});
// 將提供的密碼輸入與已保存的哈希進行比較
bcrypt.compare('somePassword', hash, function(err, match) {
if(match) {
// 密碼匹配
} else {
// 密碼不匹配
}
});
複製代碼
發送給瀏覽器的不受信任數據可能會被執行, 而不是顯示, 這一般被稱爲跨站點腳本(XSS)攻擊。使用專用庫將數據顯式標記爲不該執行的純文本內容(例如:編碼、轉義),能夠減輕這種問題。
驗證傳入請求的 body payload,並確保其符合預期要求, 若是沒有, 則快速報錯。爲了不每一個路由中繁瑣的驗證編碼, 您可使用基於 JSON 的輕量級驗證架構,好比 jsonschema 或 joi
當使用 JSON Web Tokens(例如, 經過 Passport.js), 默認狀況下, 沒有任何機制能夠從發出的令牌中撤消訪問權限。一旦發現了一些惡意用戶活動, 只要它們持有有效的標記, 就沒法阻止他們訪問系統。經過實現一個不受信任令牌的黑名單,並在每一個請求上驗證,來減輕此問題。
const jwt = require('express-jwt');
const blacklist = require('express-jwt-blacklist');
app.use(jwt({
secret: 'my-secret',
isRevoked: blacklist.isRevoked
}));
app.get('/logout', function (req, res) {
blacklist.revoke(req.user)
res.sendStatus(200);
});
複製代碼
一類保護暴力破解的中間件,好比 express-brute,應該被用在 express 的應用中,來防止暴力/字典攻擊;這類攻擊主要應用於一些敏感路由,好比 /admin
或者 /login
,基於某些請求屬性, 如用戶名, 或其餘標識符, 如正文參數等。不然攻擊者能夠發出無限制的密碼匹配嘗試, 以獲取對應用程序中特權賬戶的訪問權限。
const ExpressBrute = require('express-brute');
const RedisStore = require('express-brute-redis');
const redisStore = new RedisStore({
host: '127.0.0.1',
port: 6379
});
// Start slowing requests after 5 failed
// attempts to login for the same user
const loginBruteforce = new ExpressBrute(redisStore, {
freeRetries: 5,
minWait: 5 * 60 * 1000, // 5 minutes
maxWait: 60 * 60 * 1000, // 1 hour
failCallback: failCallback,
handleStoreError: handleStoreErrorCallback
});
app.post('/login',
loginBruteforce.getMiddleware({
key: function (req, res, next) {
// prevent too many attempts for the same username
next(req.body.username);
}
}), // error 403 if we hit this route too often
function (req, res, next) {
if (User.isValidLogin(req.body.username, req.body.password)) {
// reset the failure counter for valid login
req.brute.reset(function () {
res.redirect('/'); // logged in
});
} else {
// handle invalid user
}
}
);
複製代碼
Node.js 做爲一個具備無限權限的 root 用戶運行,這是一種廣泛的情景。例如,在 Docker 容器中,這是默認行爲。建議建立一個非 root 用戶,並保存到 Docker 鏡像中(下面給出了示例),或者經過調用帶有"-u username" 的容器來表明此用戶運行該進程。不然在服務器上運行腳本的攻擊者在本地計算機上得到無限制的權利 (例如,改變 iptable,引流到他的服務器上)
FROM node:latest
COPY package.json . RUN npm install COPY . . EXPOSE 3000
USER node
CMD ["node", "server.js"] 複製代碼
請求 body 有效載荷越大, Node.js 的單線程就越難處理它。這是攻擊者在沒有大量請求(DOS/DDOS 攻擊)的狀況下,就可讓服務器跪下的機會。在邊緣上(例如,防火牆,ELB)限制傳入請求的 body 大小,或者經過配置 express body parser
僅接收小的載荷,能夠減輕這種問題。不然您的應用程序將不得不處理大的請求, 沒法處理它必須完成的其餘重要工做, 從而致使對 DOS 攻擊的性能影響和脆弱性。
express:
const express = require('express');
const app = express();
// body-parser defaults to a body size limit of 300kb
app.use(express.json({ limit: '300kb' }));
// Request with json body
app.post('/json', (req, res) => {
// Check if request payload content-type matches json
// because body-parser does not check for content types
if (!req.is('json')) {
return res.sendStatus(415); // Unsupported media type if request doesn't have JSON body
}
res.send('Hooray, it worked!');
});
app.listen(3000, () => console.log('Example app listening on port 3000!'));
複製代碼
nginx:
http {
...
# Limit the body size for ALL incoming requests to 1 MB
client_max_body_size 1m;
}
server {
...
# Limit the body size for incoming requests to this specific server block to 1 MB
client_max_body_size 1m;
}
location /upload {
...
# Limit the body size for incoming requests to this route to 1 MB
client_max_body_size 1m;
}
複製代碼
匹配文本的用戶輸入須要大量的 CPU 週期來處理。在某種程度上,正則處理是效率低下的,好比驗證 10 個單詞的單個請求可能阻止整個 event loop 長達6秒。因爲這個緣由,偏向第三方的驗證包,好比validator.js,而不是採用正則,或者使用 safe-regex 來檢測有問題的正則表達式。
const saferegex = require('safe-regex');
const emailRegex = /^([a-zA-Z0-9])(([\-.]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$/;
// should output false because the emailRegex is vulnerable to redos attacks
console.log(saferegex(emailRegex));
// instead of the regex pattern, use validator:
const validator = require('validator');
console.log(validator.isEmail('liran.tal@gmail.com'));
複製代碼
當任務執行在運行時給出的外部代碼時(例如, 插件), 使用任何類型的沙盒執行環境保護主代碼,並隔離開主代碼和插件。這能夠經過一個專用的過程來實現 (例如:cluster.fork()), 無服務器環境或充當沙盒的專用 npm 包。
const Sandbox = require("sandbox");
const s = new Sandbox();
s.run( "lol)hai", function( output ) {
console.log(output);
//output='Synatx error'
});
// Example 4 - Restricted code
s.run( "process.platform", function( output ) {
console.log(output);
//output=Null
})
// Example 5 - Infinite loop
s.run( "while (true) {}", function( output ) {
console.log(output);
//output='Timeout'
})
複製代碼
默認狀況下, 集成的 express 錯誤處理程序隱藏錯誤詳細信息。可是, 極有可能, 您實現本身的錯誤處理邏輯與自定義錯誤對象(被許多人認爲是最佳作法)。若是這樣作, 請確保不將整個 Error 對象返回到客戶端, 這可能包含一些敏感的應用程序詳細信息。不然敏感應用程序詳細信息(如服務器文件路徑、使用中的第三方模塊和可能被攻擊者利用的應用程序的其餘內部工做流)可能會從 stack trace 發現的信息中泄露。
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
複製代碼
開發鏈中的任何步驟都應使用 MFA(多重身份驗證)進行保護, npm/Yarn 對於那些可以掌握某些開發人員密碼的攻擊者來講是一個很好的機會。使用開發人員憑據, 攻擊者能夠向跨項目和服務普遍安裝的庫中注入惡意代碼。甚至可能在網絡上公開發布。在 npm 中啓用兩層身份驗證(2-factor-authentication), 攻擊者幾乎沒有機會改變您的軟件包代碼。
每一個 web 框架和技術都有其已知的弱點,告訴攻擊者咱們使用的 web 框架對他們來講是很大的幫助。使用 session 中間件的默認設置, 能夠以相似於 X-Powered-Byheader
的方式向模塊和框架特定的劫持攻擊公開您的應用。嘗試隱藏識別和揭露技術棧的任何內容(例如:Nonde.js, express)。不然能夠經過不安全的鏈接發送cookie, 攻擊者可能會使用會話標識來標識web應用程序的基礎框架以及特定於模塊的漏洞。
// using the express session middleware
app.use(session({
secret: 'youruniquesecret', // secret string used in the signing of the session ID that is stored in the cookie
name: 'youruniquename', // set a unique name to remove the default connect.sid
cookie: {
httpOnly: true, // minimize risk of XSS attacks by restricting the client from reading the cookie
secure: true, // only send cookie over https
maxAge: 60000*60*24 // set cookie expiry length in ms
}
}));
複製代碼
路由層:
var cookieParser = require('cookie-parser');
var csrf = require('csurf');
var bodyParser = require('body-parser');
var express = require('express');
// 設置路由中間件
var csrfProtection = csrf({ cookie: true });
var parseForm = bodyParser.urlencoded({ extended: false });
var app = express();
// 咱們須要這個,由於在 csrfProtection 中 「cookie」 是正確的
app.use(cookieParser());
app.get('/form', csrfProtection, function(req, res) {
// 將 CSRFToken 傳遞給視圖
res.render('send', { csrfToken: req.csrfToken() });
});
app.post('/process', parseForm, csrfProtection, function(req, res) {
res.send('data is being processed');
});
複製代碼
展現層:
<form action="/process" method="POST">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
Favorite color: <input type="text" name="favoriteColor">
<button type="submit">Submit</button>
</form>
複製代碼
const fs = require('fs');
const exec = require('child_process').exec;
function watch() {
const child = exec('node server.js');
const watcher = fs.watch(__dirname + '/server.js', function () {
console.log('File changed, reloading.');
child.kill();
watcher.close();
watch();
});
}
watch();
複製代碼
REST 意思是表徵性狀態傳輸
使用正確的 HTTP 方法、URLs 和頭部信息來建立語義化 RESTful API
GET /gages:獲取
POST /pages:建立
GET /pages/10:獲取 pages10
PATCH /pages/10:更新 pages10
PUT /pages/10:替換 pages10
DELETE /pages/10:刪除 pages10
let app;
const express = require('express');
const routes = require('./routes');
module.exports = app = express();
app.use(express.json()); // 使用 JSON body 解析
app.use(express.methodOverride()); // 容許一個查詢參數來制定額外的 HTTP 方法
// 資源使用的路由
app.get('/pages', routes.pages.index);
app.get('/pages/:id', routes.pages.show);
app.post('/pages', routes.pages.create);
app.patch('/pages/:id', routes.pages.patch);
app.put('/pages/:id', routes.pages.update);
app.del('/pages/:id', routes.pages.remove);
複製代碼
const express = require('express');
const app = express();
const Schema = require('validate');
const xml2json = require('xml2json');
const util = require('util');
const Page = new Schema();
Page.path('title').type('string').required(); // 數據校驗確保頁面有標題
function ValidatorError(errors) { // 從錯誤對象繼承,校驗出現的錯誤在錯誤中間件處理
this.statusCode = 400;
this.message = errors.join(', ');
}
util.inherits(ValidatorError, Error);
function xmlMiddleware(req, res, next) { // 處理 xml 的中間件
if (!req.is('xml')) return next();
let body = '';
req.on('data', function (str) { // 從客戶端讀到數據時觸發
body += str;
});
req.on('end', function () {
req.body = xml2json.toJson(body.toString(), {
object: true,
sanitize: false,
});
next();
});
}
function checkValidXml(req, res, next) { // 數據校驗中間件
const page = Page.validate(req.body.page);
if (page.errors.length) {
next(new ValidatorError(page.errors)); // 傳遞錯誤給 next 阻止路由繼續運行
} else {
next();
}
}
function errorHandler(err, req, res, next) { // 錯誤處理中間件
console.error('errorHandler', err);
res.send(err.statusCode || 500, err.message);
}
app.use(xmlMiddleware); // 應用 XML 中間件到全部的請求中
app.post('/pages', checkValidXml, function (req, res) { // 特定的請求校驗 xml
console.log('Valid page:', req.body.page);
res.send(req.body);
});
app.use(errorHandler); // 添加錯誤處理中間件
app.listen(3000);
複製代碼
// 監聽用戶註冊成功消息,綁定郵件程序
const express = require('express');
const app = express();
const emails = require('./emails');
const routes = require('./routes');
app.use(express.json());
app.post('/users', routes.users.create); // 設置路由建立用戶
app.on('user:created', emails.welcome); // 監聽建立成功事件,綁定 email 代碼
module.exports = app;
複製代碼
// 用戶註冊成功發起事件
const User = require('./../models/user');
module.exports.create = function (req, res, next) {
const user = new User(req.body);
user.save(function (err) {
if (err) return next(err);
res.app.emit('user:created', user); // 當用戶成功註冊時觸發建立用戶事件
res.send('User created');
});
};
複製代碼
const express = require('express');
const WebSocketServer = require('ws').Server;
const parseCookie = express.cookieParser('some secret'); // 加載解析 cookie 中間件,設置密碼
const MemoryStore = express.session.MemoryStore; // 加載要使用的會話存儲
const store = new MemoryStore();
const app = express();
const server = app.listen(process.env.PORT || 3000);
app.use(parseCookie);
app.use(express.session({ store: store, secret: 'some secret' })); // 告知 Express 使用會話存儲和設置密碼(使用 session 中間件)
app.use(express.static(__dirname + '/public'));
app.get('/random', function (req, res) { // 測試測試用的會話值
req.session.random = Math.random().toString();
res.send(200);
});
// 設置 WebSocket 服務器,將其傳遞給 Express 服務器
// 須要傳遞已有的 Express 服務(listen 的返回對象)
const webSocketServer = new WebSocketServer({ server: server });
// 在鏈接事件給客戶端建立 WebSocket
webSocketServer.on('connection', function (ws) {
let session;
ws.on('message', function (data, flags) {
const message = JSON.parse(data);
// 客戶端發送的 JSON,須要一些代碼來解析 JSON 字符串肯定是否可用
if (message.type === 'getSession') {
parseCookie(ws.upgradeReq, null, function (err) {
// 從 HTTP 的更新請求中獲取 WebSocket 的會話 ID
// 一旦 WebSockets 服務器有一個鏈接,session ID 能夠用=從初始化請求中的 cookies 中獲取
const sid = ws.upgradeReq.signedCookies['connect.sid'];
// 從存儲中獲取用戶的會話信息
// 只須要在初始化的請求中傳遞一個引用給解析 cookie 的中間件
// 而後 session 可使用 session 存儲的 get 方法加載
store.get(sid, function (err, loadedSession) {
if (err) console.error(err);
session = loadedSession;
ws.send('session.random: ' + session.random, {
mask: false,
}); // session 加載後會把一個包含了 session 值的消息發回給客戶端
});
});
} else {
ws.send('Unknown command');
}
});
});
複製代碼
<!DOCTYPE html>
<html>
<head>
<script> const host = window.document.location.host.replace(/:.*/, ''); const ws = new WebSocket('ws://' + host + ':3000'); setInterval(function () { ws.send('{ "type": "getSession" }'); // 按期向服務器發送消息 }, 1000); ws.onmessage = function (event) { document.getElementById('message').innerHTML = event.data; }; </script>
</head>
<body>
<h1>WebSocket sessions</h1>
<div id='message'></div><br>
</body>
</html>
複製代碼
package | 描述 |
---|---|
body-parser | 解析 URL 編碼 和 JSON POST 請求的 body 數據 |
compression | 壓縮服務器響應 |
connect-timeout | 請求容許超時 |
cookie-parser | 從 HTTP 頭部信息中解析 cookies,結果放在 req.cookies |
cookie-session | 使用 cookies 來支持簡單會話 |
csurf | 在會話中添加 token,防護 CSRF 攻擊 |
errorhandler | Connect 中使用的默認錯誤處理 |
express-session | 簡單的會話處理,使用 stores 擴展來吧會話信息寫入到數據庫或文件中 |
method-override | 映射新的 HTTP 動詞到請求變量中的 _method |
morgan | 日誌格式化 |
response-time | 跟蹤響應時間 |
serve-favicon | 發送網站圖標 |
serve-index | 目錄列表 |
whost | 容許路由匹配子域名 |
JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案。
在服務器集羣,要求 session 數據共享,每臺服務器都可以讀取 session:
Header(頭部).Payload(負載).Signature(簽名):
{
"alg": "HS256", // 簽名的算法
"typ": "JWT" // token 的類型
}
複製代碼
{
// 7 個官方字段
"iss": "簽發人",
"exp": "過時時間",
"sub": "主題",
"aud": "受衆",
"nbf": "生效時間",
"iat": "簽發時間",
"jti": "編號",
// 定義私有字段
"name": "Chenng"
}
複製代碼
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret) # secret 祕鑰只有服務器知道
複製代碼
const Koa = require('koa');
const app = new Koa();
const mid1 = async (ctx, next) => {
ctx.body = 'Hi';
await next(); // next 執行下一個中間件
ctx.body += ' there';
};
const mid2 = async (ctx, next) => {
ctx.type = 'text/html; chartset=utf-8';
await next();
};
const mid3 = async (ctx, next) => {
ctx.body += ' chenng';
await next();
};
app.use(mid1);
app.use(mid2);
app.use(mid3);
app.listen(2333);
// Hi chenng there
複製代碼
router
.get('/api/dynamic_image/codewars', async (ctx, next) => {
const res = await axios.get('https://www.codewars.com/users/ringcrl');
const [, kyu, score] = res.data
.match(/<div class="stat"><b>Rank:<\/b>(.+?)<\/div><div class="stat"><b>Honor:<\/b>(.+?)<\/div>/);
const svg = ` <svg xmlns="http://www.w3.org/2000/svg" width="80" height="20"> <rect x="0" y="0" width="80" height="20" fill="#fff" stroke-width="2" stroke="#cccccc"></rect> <rect x="0" y="0" width="50" height="20" fill="#5b5b5b"></rect> <text x="5" y="15" class="small" fill="#fff" style="font-size: 14px;">${kyu}</text> <rect x="50" y="0" width="30" height="20" fill="#3275b0"></rect> <text x="53" y="15" class="small" fill="#fff" style="font-size: 14px">${score}</text> </svg> `;
ctx.set('Content-Type', 'image/svg+xml');
ctx.body = Buffer.from(svg);
await next();
});
複製代碼
API 通用資源網站 ProgrammableWeb(www.programmableweb.com)中有各類已經公開的 Web API 文檔,多觀察一下
利用 openssl 生成公鑰私鑰
生成公鑰:openssl genrsa -out rsa_private_key.pem 1024
生成私鑰:openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
複製代碼
const crypto = require('crypto');
const fs = require('fs');
const publicKey = fs.readFileSync(`${__dirname}/rsa_public_key.pem`).toString('ascii');
const privateKey = fs.readFileSync(`${__dirname}/rsa_private_key.pem`).toString('ascii');
console.log(publicKey);
console.log(privateKey);
const data = 'Chenng';
console.log('content: ', data);
//公鑰加密
const encodeData = crypto.publicEncrypt(
publicKey,
Buffer.from(data),
).toString('base64');
console.log('encode: ', encodeData);
//私鑰解密
const decodeData = crypto.privateDecrypt(
privateKey,
Buffer.from(encodeData, 'base64'),
);
console.log('decode: ', decodeData.toString());
複製代碼
const redis = require('redis');
const redisClient = redis.createClient();
const getAsync = promisify(redisClient.get).bind(redisClient);
let codewarsRes = JSON.parse(await getAsync('codewarsRes'));
if (!codewarsRes) {
const res = await axios.get('https://www.codewars.com/users/ringcrl');
codewarsRes = res.data;
redisClient.set('codewarsRes', JSON.stringify(codewarsRes), 'EX', 86000);
}
複製代碼
const schedule = require('node-schedule');
const axios = require('axios');
schedule.scheduleJob('* 23 59 * *', function () {
axios.get('https://static.chenng.cn/api/dynamic_image/leetcode_problems');
axios.get('https://static.chenng.cn/api/dynamic_image/leetcode');
axios.get('https://static.chenng.cn/api/dynamic_image/codewars');
});
複製代碼