const puppeteer = require('puppeteer');
const pageUrl = 'https://some-url.com';
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', (interceptedRequest) => {
// Don't intercept main document request
if (interceptedRequest.url === pageUrl) {
interceptedRequest.continue();
return;
}
// Intercept if request url starts with https
if (interceptedRequest.url.startsWith('https://')) {
interceptedRequest.continue({
// Replace https:// in url with http://
url: interceptedRequest.url.replace('https://', 'http://'),
});
return;
}
// Don't override other requests
interceptedRequest.continue();
})
await page.goto(pageUrl);
await browser.close();
})();
31 篇博文 含有标签「node.js」
查看所有标签记一次 redis cluster 事务 (transaction) 翻车及分析总结
发现生产环境的业务报了好多错误, 涉及的 Node.js 代码是一个基于 Redis 的频率计数器,那部分逻辑大概是这样
// 查询并增加一次计数
async incr (id) {
const key = `${this.namespace}:${id}`
const now = getMicrotime()
const start = now - this.duration * 1000
const operations = [
['zremrangebyscore', key, 0, start],
['zcard', key],
['zadd', key, now, now],
['pexpire', key, this.duration]
]
const res = await this.redis.multi(operations).exec()
const count = toNumber(res[1][1])
return count
}
错误是:
Cannot read property '1' of undefined
Mac 环境下 node 安装 canvas@2.6.1 出现错误
Mac 环境下 node 安装 canvas@2.6.1 出现以下错误时
node: cairo-pattern.c:1127: cairo_pattern_destroy: Assertion failed. none - catched error
使用 brew 安装一下以下几个库
brew install pixman cairo pango
不过你可能会遇到 python2.x 升级失败的问题
可以试试
brew uninstall python@2
brew install python
brew upgrade python
升级到 python3.x
来源: https://github.com/Automattic/node-canvas/issues/1065#issuecomment-373381272
axios 下载文件并保存到本地
const Fs = require('fs')
const Path = require('path')
const Axios = require('axios')
async function downloadImage () {
const url = 'https://unsplash.com/photos/AaEQmoufHLk/download?force=true'
const path = Path.resolve(__dirname, 'images', 'code.jpg')
const writer = Fs.createWriteStream(path)
const response = await Axios({
url,
method: 'GET',
responseType: 'stream'
})
response.data.pipe(writer)
return new Promise((resolve, reject) => {
writer.on('finish', resolve)
writer.on('error', reject)
})
}
downloadImage()
主要注意的是
- responseType: 'stream'
- response.data.pipe(writer)
nodemailer 使用 126 邮箱时的踩坑,报错 Invalid login 535 Error authentication failed
错误
如果你在使用 nodemailer smtp 发送邮箱时遇到了以下错误:
Error: Invalid login: 535 Error: authentication failed
也许是因为需要使用「客户端授权码」的问题。
解决
const _ = require('lodash')
const nodemailer = require('nodemailer')
async function main () {
const mailer = nodemailer.createTransport({
host: 'smtp.126.com',
port: 465,
pool: true,
secure: true,
auth: {
type: 'login',
user: 'xxxxx@126.com',
pass: 'xxxxx' // 如果开启了客户端授权码,则这里需要填写客户端授权码
},
tls: {
rejectUnauthorized: false
}
})
const sendMailOptions = {
from: 'xxxxxxx@126.com',
to: 'xxxxxx@163.com',
subject: ' 测试主题 ',
html: ' 测试内容 '
}
const result = await mailer.sendMail(sendMailOptions)
if (!_.startsWith(_.get(result, 'response'), '250 Mail OK')) {
return Promise.reject(new Error('Send mail fail'))
}
return result.response
}
main()
参考
alinode 和 官方的 node 版本对应关系
可以通过这个地址访问到
http://alinode.aliyun.com/dist/new-alinode/alinode.json
其中
{
"version": "v4.7.2",
"date": "2019-03-11",
"files": "linux-x64,osx-x64-tar",
"npm": "6.4.1",
"v8": "6.8.275.32",
"uv": "1.23.2",
"zlib": "1.2.11",
"openssl": "1.1.0j",
"modules": "64",
"node": "v10.15.3",
"notes": [
""
]
}
version 是 alinode 版本,node 是官方 node 版本
nodejs 在 build docker 镜像时 grpc 库安装超时的解决办法
在 Dockerfile 执行命令时,添加以下选项
FROM node
COPY . .
RUN npm install --grpc_node_binary_host_mirror=https://npm.taobao.org/mirrors/
CMD ['npm', 'start']
node-worker-farm 多进程函数执行库源码解读和应用
node-worker-farm 主要是用来将特定的逻辑,作为子进程的方式执行,由 worker-farm 来统一调度,分发调用任务。
模块构造函数
https://github.com/rvagg/node-worker-farm/blob/master/lib/index.js
可以看到构造函数主要是初始化 farm 容器,将给定的脚本文件路径加载进来等待调用的动作的分发。
可以看到入口有一个 farms 数组,用于存放 farm 容器,当你初始化多个 farm 实例的时候,都会将实例存储在这里,当你需要结束进程时,直接调用 node-worker-farm 暴露的 end 方法,即会销毁所有的 farm 实例。
不过这个特性也许是个坑吧,有人可能会不小心销毁掉不需要销毁的实例。
Farm
Farm 即是整个模块的核心了,它负责的功能有:
控制子进程的创建/销毁
新进来的函数执行任务的分发
控制调用的并发
各个参数的作用:
参数名 | 说明 | 默认值 |
---|---|---|
maxCallsPerWorker | 每个子进程最多可以处理多少调用 | Infinity |
maxConcurrentWorkers | 最大并发子进程数 | CPU 核数 |
maxConcurrentCallsPerWorker | 每个子进程最大的并发处理数 | 10 |
maxConcurrentCalls | 全局最大并发处理数 | Infinity |
maxCallTime | 单个 worker 最大处理时间 | Infinity |
maxRetries | 最大重试次数 | Infinity |
forcedKillTime | 在进程退出时,如果子进程未正常退出,则会强制退出 | 100ms |
autoStart | 初始化时,就自动最大数量的子进程 | false |
onChild | 创建子进程时触发的函数 | 空函数 |
函数调用
每次函数的执行是,构造了一个方法、参数的对象,发送给子进程排队处理。如果有限制最大全局并发数时,达到并发会抛出错误。
https://github.com/rvagg/node-worker-farm/blob/master/lib/farm.js#L312
每次调用函数在内部会执行 addCall
方法,这时就是构造了一个调用的对象信息。将调用信息存入全局队列中,执行 processQueue
,进行分发。
当前活跃的子进程个数小于最大并发子进程数时,创建一个子进程。这时,检查所有子进程正在处理的调用是否小于子进程的并发处理数。小于则将任务分配给这些子进程。
当发送任务时,如果有配置超时时间,则会注册一个超时回调,若超时,则整个子进程会被杀死,如果你的子进程中有多个调用正在执行,那么都会被干掉。
处理完之后,通过 receive
方法进行后续的处理。
应用
现在默认的情况下,是最调用效率最优的方案,因为子进程被创建后,有调用的情况下,一直不会被销毁,省去了冷启动的时间。
但是,如果你需要在超时的情况下,cancel 掉正在处理的逻辑,maxCallTime
可以实现这个需求,但有个问题是,子进程被杀死时,所有其他的调用都会被终止,这不符合我们的期望,因此我们需要将 maxCallsPerWorker
设为 1 ,让每个子进程同时只处理一个任务,处理完就退出,这样如果任务超时,不会对其他的任务造成影响。
思考
这样做的执行时间将会大幅增加,还有什么办法能够保证任务互不影响的情况,尽可能不降低执行效率呢。
目前想到的思路是,每次虽然只执行一个,但是每次执行完后不将子进程销毁,知道超时的时候才销毁一次,这样的话也可以减少每次创建子进程带来的开销。
你还有更好的方案吗?欢迎在下面留言讨论。
小技巧,Node.js 怎么在程序中打开一个文件,并监听文件退出
var vim = require('child_process').spawn('vim', ['test.txt'], {stdio: 'inherit'});
vim.on('exit', () => {
console.log('saved txt')
process.exit(0)
});
Node.js 中监听 redis key 过期事件
It is in fact possible to listen to the "expired" type keyevent notification using a subscribed client to the specific channel ( and listening to its message event.
通过 subscribe client
可以监听 __keyevent@{db}__:expired
( db
是你传入的配置 ) 频道来接收过期的事件 ,
const redis = require('redis')
const CONF = {db:3}
let pub, sub
// Activate "notify-keyspace-events" for expired type events
pub = redis.createClient(CONF)
pub.send_command('config', ['set','notify-keyspace-events','Ex'], subscribeExpired)
// Subscribe to the "notify-keyspace-events" channel used for expired type events
function subscribeExpired(e, r){
sub = redis.createClient(CONF)
const expired_subKey = `__keyevent@${CONF.db}__:expired`
sub.subscribe(expired_subKey, function () {
console.log(' [i] Subscribed to "'+expired_subKey+'" event channel : '+r)
sub.on('message', function (chan, msg){
console.log('[expired]', msg)}
)
TestKey()
})
}
//例如,设置一个 key 并设置 10s 超时
function TestKey(){
pub.set('testing', 'redis notify-keyspace-events : expired')
pub.expire('testing', 10)
}