NodeJS中还需要补充的点

最后编辑于 2019-04-13 03:19:50

NodeJS Javascript
8 461
记录下我在NodeJS中还需要补充了解的知识点, 并在以后可以进行梳理.

全局变量

require(id)

  • 内建模块直接从内存加载
  • 文件模块通过文件查找定位到文件
  • 包通过 package.json 里面的 mian 字段查找入口文件

module.exports

  1. // 通过如下模块包装得到
  2. (funciton (exports, require, module, __filename, __dirname) { // 包装头
  3. }); // 包装尾

module.exports 和 exports

  1. (funciton(exports, require, module, __filename, __dirname) { // 包装头
  2. console.log('hello world!') // 原始文件
  3. }); // 包装尾

exports

  • exports 是 module 的属性,默认情况是空对象
  • require 一个模块实际得到的是该模块的 exports 属性
  • exports.xxx 导出具有多个属性的对象
  • module.exports = xxx 导出一个对象

process

查看 PATH or 设置 PATH

  1. console.log(process.env.PATH.split(':').join('\n'));
  2. process.env.PATH += ':/a_new_path_to_executables';

获取信息

  1. // 获取平台信息
  2. process.arch // x64
  3. process.platform // darwin
  4. // 获取内存使用情况
  5. process.memoryUsage();
  6. // 获取命令行参数
  7. process.argv

nextTick

process.nextTick 方法允许你把一个回调放在下一次时间轮询队列的头上,这意味着可以用来延迟执行,结果是比 setTimeout 更有效率。

Buffer

events (EventEmitter)

Stream

流是基于事件的 API,用于管理和处理数据。

  • 流是能够读写的
  • 是基于事件实现的一个实例

使用流 + gzip

  1. const http = require('http');
  2. const fs = require('fs');
  3. const zlib = require('zlib');
  4. http.createServer((req, res) => {
  5. res.writeHead(200, {
  6. 'content-encoding': 'gzip',
  7. });
  8. fs.createReadStream(`${__dirname}/index.html`)
  9. .pipe(zlib.createGzip())
  10. .pipe(res);
  11. }).listen(8000);

流的错误处理

  1. const fs = require('fs');
  2. const stream = fs.createReadStream('not-found');
  3. stream.on('error', (err) => {
  4. console.trace();
  5. console.error('Stack:', err.stack);
  6. console.error('The error raised was:', err);
  7. });

文件系统

POSIX 文件系统

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 读取一个文件的数据
  1. const fs = require('fs');
  2. const assert = require('assert');
  3. const fd = fs.openSync('./file.txt', 'w+');
  4. const writeBuf = new Buffer('some data to write');
  5. fs.writeSync(fd, writeBuf, 0, writeBuf.length, 0);
  6. const readBuf = new Buffer(writeBuf.length);
  7. fs.readSync(fd, readBuf, 0, writeBuf.length, 0);
  8. assert.equal(writeBuf.toString(), readBuf.toString());
  9. fs.closeSync(fd);

文件监控

fs.watchFilefs.watch 低效,但更好用。

文件锁

协同多个进程同时访问一个文件,保证文件的完整性以及数据不能丢失:

  • 强制锁(在内核级别执行)

  • 咨询锁(非强制,只在涉及到进程订阅了相同的锁机制)

    • node-fs-ext 通过 flock 锁住一个文件
  • 使用锁文件

    • 进程 A 尝试创建一个锁文件,并且成功了
    • 进程 A 已经获得了这个锁,可以修改共享的资源
    • 进程 B 尝试创建一个锁文件,但失败了,无法修改共享的资源

Node 实现锁文件

  • 使用独占标记创建锁文件
  • 使用 mkdir 创建锁文件

独占标记

  1. // 所有需要打开文件的方法,fs.writeFile、fs.createWriteStream、fs.open 都有一个 x 标记
  2. // 这个文件应该已独占打开,若这个文件存在,文件不能被打开
  3. fs.open('config.lock', 'wx', (err) => {
  4. if (err) { return console.err(err); }
  5. });
  6. // 最好将当前进程号写进文件锁中
  7. // 当有异常的时候就知道最后这个锁的进程
  8. fs.writeFile(
  9. 'config.lock',
  10. process.pid,
  11. { flogs: 'wx' },
  12. (err) => {
  13. if (err) { return console.error(err) };
  14. },
  15. );

mkdir 文件锁

独占标记有个问题,可能有些系统不能识别 0_EXCL 标记。另一个方案是把锁文件换成一个目录,PID 可以写入目录中的一个文件。

  1. fs.mkidr('config.lock', (err) => {
  2. if (err) { return console.error(err); }
  3. fs.writeFile(`/config.lock/${process.pid}`, (err) => {
  4. if (err) { return console.error(err); }
  5. });
  6. });

lock 模块实现

lockfile

  1. const fs = require('fs');
  2. const lockDir = 'config.lock';
  3. let hasLock = false;
  4. exports.lock = function (cb) { // 获取锁
  5. if (hasLock) { return cb(); } // 已经获取了一个锁
  6. fs.mkdir(lockDir, function (err) {
  7. if (err) { return cb(err); } // 无法创建锁
  8. fs.writeFile(lockDir + '/' + process.pid, function (err) { // 把 PID写入到目录中以便调试
  9. if (err) { console.error(err); } // 无法写入 PID,继续运行
  10. hasLock = true; // 锁创建了
  11. return cb();
  12. });
  13. });
  14. };
  15. exports.unlock = function (cb) { // 解锁方法
  16. if (!hasLock) { return cb(); } // 如果没有需要解开的锁
  17. fs.unlink(lockDir + '/' + process.pid, function (err) {
  18. if (err) { return cb(err); }
  19. fs.rmdir(lockDir, function (err) {
  20. if (err) return cb(err);
  21. hasLock = false;
  22. cb();
  23. });
  24. });
  25. };
  26. process.on('exit', function () {
  27. if (hasLock) {
  28. fs.unlinkSync(lockDir + '/' + process.pid); // 如果还有锁,在退出之前同步删除掉
  29. fs.rmdirSync(lockDir);
  30. console.log('removed lock');
  31. }
  32. });

网络

TCP

NodeJS 使用 net 模块创建 TCP 连接和服务。

UDP

利用 dgram 模块创建数据报 socket,然后利用 socket.send 发送数据。

文件发送服务

  1. const dgram = require('dgram');
  2. const fs = require('fs');
  3. const port = 41230;
  4. const defaultSize = 16;
  5. function Client(remoteIP) {
  6. const inStream = fs.createReadStream(__filename); // 从当前文件创建可读流
  7. const socket = dgram.createSocket('udp4'); // 创建新的数据流 socket 作为客户端
  8. inStream.on('readable', function () {
  9. sendData(); // 当可读流准备好,开始发送数据到服务器
  10. });
  11. function sendData() {
  12. const message = inStream.read(defaultSize); // 读取数据块
  13. if (!message) {
  14. return socket.unref(); // 客户端完成任务后,使用 unref 安全关闭它
  15. }
  16. // 发送数据到服务器
  17. socket.send(message, 0, message.length, port, remoteIP, function () {
  18. sendData();
  19. }
  20. );
  21. }
  22. }
  23. function Server() {
  24. const socket = dgram.createSocket('udp4'); // 创建一个 socket 提供服务
  25. socket.on('message', function (msg) {
  26. process.stdout.write(msg.toString());
  27. });
  28. socket.on('listening', function () {
  29. console.log('Server ready:', socket.address());
  30. });
  31. socket.bind(port);
  32. }
  33. if (process.argv[2] === 'client') { // 根据命令行选项确定运行客户端还是服务端
  34. new Client(process.argv[3]);
  35. } else {
  36. new Server();
  37. }

HTTP

HTTP 代理

  • ISP 使用透明代理使网络更加高效
  • 使用缓存代理服务器减少宽带
  • Web 应用程序的 DevOps 利用他们提升应用程序性能
  1. const http = require('http');
  2. const url = require('url');
  3. http.createServer(function(req, res) {
  4. console.log('start request:', req.url);
  5. const options = url.parse(req.url);
  6. console.log(options);
  7. options.headers = req.headers;
  8. const proxyRequest = http.request(options, function(proxyResponse) { // 创建请求来复制原始的请求
  9. proxyResponse.on('data', function(chunk) { // 监听数据,返回给浏览器
  10. console.log('proxyResponse length:', chunk.length);
  11. res.write(chunk, 'binary');
  12. });
  13. proxyResponse.on('end', function() { // 追踪代理请求完成
  14. console.log('proxied request ended');
  15. res.end();
  16. });
  17. res.writeHead(proxyResponse.statusCode, proxyResponse.headers); // 发送头部信息给服务器
  18. });
  19. req.on('data', function(chunk) { // 捕获从浏览器发送到服务器的数据
  20. console.log('in request length:', chunk.length);
  21. proxyRequest.write(chunk, 'binary');
  22. });
  23. req.on('end', function() { // 追踪原始的请求什么时候结束
  24. console.log('original request ended');
  25. proxyRequest.end();
  26. });
  27. }).listen(8888); // 监听来自本地浏览器的连接

DNS 请求

使用 dns 模块创建 DNS 请求。

  • A:dns.resolve,A 记录存储 IP 地址
  • TXT:dns.resulveTxt,文本值可以用于在 DNS 上构建其他服务
  • SRV:dns.resolveSrv,服务记录定义服务的定位数据,通常包含主机名和端口号
  • NS:dns.resolveNs,指定域名服务器
  • CNAME:dns.resolveCname,相关的域名记录,设置为域名而不是 IP 地址
  1. const dns = require('dns');
  2. dns.resolve('www.chenng.cn', function (err, addresses) {
  3. if (err) {
  4. console.error(err);
  5. }
  6. console.log('Addresses:', addresses);
  7. });

子进程

4个异步方法:exec、execFile、fork、spawn

3个同步方法:execSync、execFileSync、spawnSync

execFile

会把输出结果缓存好,通过回调返回最后结果或者异常信息

  1. const cp = require('child_process');
  2. cp.execFile('echo', ['hello', 'world'], (err, stdout, stderr) => {
  3. if (err) { console.error(err); }
  4. console.log('stdout: ', stdout);
  5. console.log('stderr: ', stderr);
  6. });

spawn

  • 通过流可以使用有大量数据输出的外部应用,节约内存
  • 使用流提高数据响应效率
  • spawn 方法返回一个 I/O 的流接口

exec

  • 只有一个字符串命令
  1. const cp = require('child_process');
  2. cp.exec(`cat ${__dirname}/messy.txt | sort | uniq`, (err, stdout, stderr) => {
  3. console.log(stdout);
  4. });

fork

  • fork 方法会开发一个 IPC 通道,不同的 Node 进程进行消息传送
  • 一个子进程消耗 30ms 启动时间和 10MB 内存
  • 子进程:process.on('message')process.send()
  • 父进程:child.on('message')child.send()

父子通信

  1. // parent.js
  2. const cp = require('child_process');
  3. const child = cp.fork('./child', { silent: true });
  4. child.send('monkeys');
  5. child.on('message', function (message) {
  6. console.log('got message from child', message, typeof message);
  7. })
  8. child.stdout.pipe(process.stdout);
  9. setTimeout(function () {
  10. child.disconnect();
  11. }, 3000);
  1. // child.js
  2. process.on('message', function (message) {
  3. console.log('got one', message);
  4. process.send('no pizza');
  5. process.send(1);
  6. process.send({ my: 'object' });
  7. process.send(false);
  8. process.send(null);
  9. });
  10. console.log(process);

常用技巧

退出时杀死所有子进程

保留对由 spawn 返回的 ChildProcess 对象的引用,并在退出主进程时将其杀死

  1. const spawn = require('child_process').spawn;
  2. const children = [];
  3. process.on('exit', function () {
  4. console.log('killing', children.length, 'child processes');
  5. children.forEach(function (child) {
  6. child.kill();
  7. });
  8. });
  9. children.push(spawn('/bin/sleep', ['10']));
  10. children.push(spawn('/bin/sleep', ['10']));
  11. children.push(spawn('/bin/sleep', ['10']));
  12. setTimeout(function () { process.exit(0); }, 3000);

Cluster 的理解

  • 解决 NodeJS 单进程无法充分利用多核 CPU 问题
  • 通过 master-cluster 模式可以使得应用更加健壮

round-robin轮询 ?

异常处理

处理未捕获的异常

  • 除非开发者记得添加.catch语句,在这些地方抛出的错误都不会被 uncaughtException 事件处理程序来处理,然后消失掉。
  • Node 应用不会奔溃,但可能导致内存泄露
  1. process.on('uncaughtException', (error) => {
  2. // 我刚收到一个从未被处理的错误
  3. // 现在处理它,并决定是否需要重启应用
  4. errorManagement.handler.handleError(error);
  5. if (!errorManagement.handler.isTrustedError(error)) {
  6. process.exit(1);
  7. }
  8. });
  9. process.on('unhandledRejection', (reason, p) => {
  10. // 我刚刚捕获了一个未处理的promise rejection,
  11. // 因为我们已经有了对于未处理错误的后备的处理机制(见下面)
  12. // 直接抛出,让它来处理
  13. throw reason;
  14. });

Domain 模块

Domain(域) 简化异步代码的异常处理,可以捕捉处理try catch无法捕捉的异常。引入 Domain 模块 语法格式如下:

  1. var domain = require("domain")

domain模块,把处理多个不同的IO的操作作为一个组。注册事件和回调到domain,当发生一个错误事件或抛出一个错误时,domain对象会被通知,不会丢失上下文环境,也不导致程序错误立即退出,与process.on('uncaughtException')不同。

Domain 模块可分为隐式绑定和显式绑定:

  • 隐式绑定: 把在domain上下文中定义的变量,自动绑定到domain对象
  • 显式绑定: 把不是在domain上下文中定义的变量,以代码的方式绑定到domain对象

实例

检测有漏洞的依赖项

audit

应用安全清单

helmet 设置安全响应头

检测头部配置:Security Headers

应用程序应该使用安全的 header 来防止攻击者使用常见的攻击方式,诸如跨站点脚本攻击(XSS)、跨站请求伪造(CSRF)。可以使用模块 helmet 轻松进行配置。

  • 构造

    • X-Frame-Options:sameorigin。提供点击劫持保护,iframe 只能同源。
  • 传输

    • Strict-Transport-Security:max-age=31536000; includeSubDomains。强制 HTTPS,这减少了web 应用程序中错误通过 cookies 和外部链接,泄露会话数据,并防止中间人攻击
  • 内容

    • X-Content-Type-Options:nosniff。阻止从声明的内容类型中嗅探响应,减少了用户上传恶意内容造成的风险
    • Content-Type:text/html;charset=utf-8。指示浏览器将页面解释为特定的内容类型,而不是依赖浏览器进行假设
  • XSS

    • X-XSS-Protection:1; mode=block。启用了内置于最新 web 浏览器中的跨站点脚本(XSS)过滤器
  • 下载

    • X-Download-Options:noopen。
  • 缓存

    • Cache-Control:no-cache。web 应中返回的数据可以由用户浏览器以及中间代理缓存。该指令指示他们不要保留页面内容,以免其他人从这些缓存中访问敏感内容
    • Pragma:no-cache。同上
    • Expires:-1。web 响应中返回的数据可以由用户浏览器以及中间代理缓存。该指令通过将到期时间设置为一个值来防止这种情况。
  • 访问控制

    • Access-Control-Allow-Origin:not *。‘Access-Control-Allow-Origin: *’ 默认在现代浏览器中禁用
    • X-Permitted-Cross-Domain-Policies:master-only。指示只有指定的文件在此域中才被视为有效
  • 内容安全策略

    • Content-Security-Policy:内容安全策略需要仔细调整并精确定义策略
  • 服务器信息

    • Server:不显示。

ratelimit 限制并发请求

koa-ratelimit

ratelimiter

纯文本机密信息放置

存储在源代码管理中的机密信息必须进行加密和管理 (滚动密钥(rolling keys)、过期时间、审核等)。使用 pre-commit/push 钩子防止意外提交机密信息。

ORM/ODM 库防止查询注入漏洞

要防止 SQL/NoSQL 注入和其他恶意攻击, 请始终使用 ORM/ODM 或 database 库来转义数据或支持命名的或索引的参数化查询, 并注意验证用户输入的预期类型。不要只使用 JavaScript 模板字符串或字符串串联将值插入到查询语句中, 因为这会将应用程序置于广泛的漏洞中。

库:

  • TypeORM
  • sequelize
  • mongoose
  • Knex
  • Objection.js
  • waterline

限制每个用户允许的登录请求

利用redis判断请求次数, 并限制, 可参考

  1. const ExpressBrute = require('express-brute');
  2. const RedisStore = require('express-brute-redis');

需要注意

使用非 root 用户运行 Node.js

防止 RegEx 让 NodeJS 过载

在沙箱中运行不安全代码

隐藏客户端的错误详细信息

  1. // production error handler
  2. // no stacktraces leaked to user
  3. app.use(function(err, req, res, next) {
  4. res.status(err.status || 500);
  5. res.render('error', {
  6. message: err.message,
  7. error: {}
  8. });
  9. });

对 npm 或 Yarn,配置 2FA

开发链中的任何步骤都应使用 MFA(多重身份验证)进行保护, npm/Yarn 对于那些能够掌握某些开发人员密码的攻击者来说是一个很好的机会。使用开发人员凭据, 攻击者可以向跨项目和服务广泛安装的库中注入恶意代码。甚至可能在网络上公开发布。在 npm 中启用两层身份验证(2-factor-authentication), 攻击者几乎没有机会改变您的软件包代码。

itnext.io/eslint…


作者:Zyao89;转载请保留此行,谢谢;
原文网站:https://www.2o3t.cn
祝你好运,谢谢支持

相关推荐

分享一下

支持一下

文章目录

    TOP