跳到主要内容

31 篇博文 含有标签「node.js」

查看所有标签

· 阅读需 3 分钟

首先,使用express框架,能够接收到支付宝发来的POST notify请求,但是解析出来的body一直为空对象,然后将整个请求log出来查看,发现支付宝发来的Content-Type是一个奇葩的

application/x-www-form-urlencoded;text/html;charset=utf-8

这TM什么鬼玩意,到底是form还是html,于是bodyParser就扑街表示不认识了

解决方式也非常简单暴力,在bodyParser中间件之前添加一个中间件

app.use(function (req, res, next) {
if (req.url == '/alipay' || req.url == '/alipay/test'){
req.headers['content-type'] = 'application/x-www-form-urlencoded';
}
next();
});

其次是校验notify的签名时,支付宝官方给的说明是这样的 untitled1.png

如果没注意仔细看的话,就会以为是queryString的方式拼接,于是我用了nodejs自带的query-string库,做好了queryString.stringify(),一切完美。但是签名校验根本过不去

后来突然想到,难道不要urlencode,不是queryString,于是把代码替换成了手工拼接字符串而不要urlencode。校验就过去了……

原文 https://blog.bangbang93.com/2015/12/08/nodejs%E9%9B%86%E6%88%90%E6%94%AF%E4%BB%98%E5%AE%9D%E6%89%80%E9%81%87%E5%88%B0%E7%9A%84%E5%9D%91.moe?utm_source=tuicool&utm_medium=referral

· 阅读需 2 分钟

之前的一篇文章说到了维护一个cookie池来降低500的概率。

记搜狗微信号搜索反爬虫

在知乎上看到有人说只需要SNUID替换,其他的不影响。 所以我就写了一个SNUID的池,存储在redis中。

思路:

  1. 通过随机关键字不带cookie请求weixin.sogou.com
  2. 从请求的header中抽取SNUID缓存在redis中。
  3. 每6个小时将SNUID池更新一遍。

代码在这里: https://github.com/luoyjx/weixin-crawler-es5

· 阅读需 2 分钟

1、创建时的回调

beforeValidate: fn(values, cb)
afterValidate: fn(values, cb)
beforeCreate: fn(values, cb)
afterCreate: fn(newlyInsertedRecord, cb)

2、修改的时候回调

beforeValidate: fn(valuesToUpdate, cb)
afterValidate: fn(valuesToUpdate, cb)
beforeUpdate: fn(valuesToUpdate, cb)
afterUpdate: fn(updatedRecord, cb)

3、销毁的时候回调

beforeDestroy: fn(criteria, cb)
afterDestroy: fn(destroyedRecords, cb)

如在创建用户的时候对密码机密操作:

var bcrypt = require('bcrypt');

module.exports = {
attributes: {
username: {
type: 'string',
required: true
},
password: {
type: 'string',
minLength: 6,
required: true,
columnName: 'encrypted_password'
}
},
beforeCreate: function (values, cb) {
bcrypt.hash(values.password, 10, function(err, hash) {
if(err) return cb(err);
values.password = hash;
cb();
});
}
};

· 阅读需 2 分钟

错误信息

以下是报的错误

error: Error: The hook `orm` is taking too long to load.
Make sure it is triggering its `initialize()` callback, or else set `sails.config.orm._hookTimeout to a higher value (currently 20000)
at tooLong [as _onTimeout] (C:\Users\KAMI\AppData\Roaming\npm\node_modules\sails\lib\app\private\loadHooks.js:92:21)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15

解决方案

在config目录下创建两个文件,orm和pubsub,不过据回答的作者说是名字并不重要。

// config/orm.js
module.exports.orm = {
_hookTimeout: 60000 // I used 60 seconds as my new timeout
};
// config/pubsub.js
module.exports.pubsub = {
_hookTimeout: 60000 // I used 60 seconds as my new timeout
};

不建议直接在 node_modules 中修改

原问题地址:http://stackoverflow.com/questions/28524926/the-hook-orm-taking-too-long-to-load

· 阅读需 9 分钟

引言

nodejs 中以模块来组织代码,加载模块是怎样的一个过程呢?require 又是怎么在运作?

模块

nodejs 中以模块为最小单位来组织代码,类似于 Java 中的 package 概念,而在 nodejs 中要搞清楚模块,就要搞清楚模块是怎么样加载的。

源码分析

我们都知道加载一个模块是使用 require 函数来实现的,那么先从 require 函数下手。

1.Module.prototype.require

我们从 module.js 里找,发现有个 require 方法,Module.prototype.require

Module.prototype.require = function(path) {
assert(path, 'missing path');
assert(util.isString(path), 'path must be a string');
return Module._load(path, this);
};

2.Module._load

通过路径加载一个模块。 方法注释上给出如下解释:

从缓存中查找所要加载的模块

  1. 如果一个模块已经存在于缓存中:直接返回它的 exports 对象
  2. 如果模块是一个本地模块,调用 'NativeModule.require()' 方法,filename 作为参数,并返回结果
  3. 否则,使用这个文件创建一个新模块并把它加入缓存中。在加载它只会返回 exports 对象。

3.Module._resolveFilename

而通常我们在某个模块中给出的路径都是一个模块的相对路径,那么会先调用 Module._resolveFilename 这个方法来查找下这个文件的真实路径的全路径文件名。

而在 Module._resolveFilename 这个方法中,首先会去检查,本地模块是否有这个模块,如果有,直接返回,如果没有,继续往下查找。

4.Module._resolveLookupPaths

接着就会碰到 Module._resolveLookupPaths 方法了,从代码来看,他返回了一个数组,数组的第一个元素是模块的 id,而第二个元素是模块的 paths。

这些 paths 接下来会用来查找是否存在需要 require 的这个模块了,存在就会返回一个文件名。

而接下来通过这个 filename 来到 Module._cache 中查找是否,有则返回 module.exports 对象,没有缓存则又会查找一次本地模块,不存在这个本地模块,就新创建一个模块,并在 cache 中缓存它。这就是我们加载了一个模块之后,第二次在别处加载时也不会重新加载的原因。

5.Module.load

而在创建模块之后,还有个装载的过程 Module.load, 装载的过程中会将几种扩展名的文件执行不同的操作:

  • .js Module._complie,运行这个 js 并包裹在适当的作用域中,并传入参数 require, module, exports
  • .json 读文件之后,使用 JSON.parse 转成对象
  • .node 使用 process.dlopen 加载扩展

如果 js 文件中包含模块引用,那么还会继续重复以上操作的。

这里会 try catch 一下,如果装载失败,就会从 cache 中将这个模块删除。

try {
module.load(filename);
hadException = false;
} finally {
if (hadException) {
delete Module._cache[filename];
}
}

热部署

这里的细节给了我们一个思路,即当我们程序运行时,加载了某个模块,而我们可能在运行过程中修改了代码,或者是重新部署了代码,那么我们可以通过先删除 cache 中的内容,再加载一次,来实现动态加载,也可以说是热部署。 具体的做法可以参考这个文章 http://fex.baidu.com/blog/2015/05/nodejs-hot-swapping/

到这里一个模块基本上已经加载完成了。

· 阅读需 5 分钟

在 nodejs 模块中,如果希望向外暴露出模块中的方法和变量,需要使用 module.exports 或者 exports,但是他们的意义却不同。

module.exports

module 表示该模块对象,在 module 对象中有个 exports 的属性,默认值为空对象{};exports 是模块往外暴露方法或者变量的接口,可以暴露变量或者方法:

    var a =  10;
module.exports = a;

var a = 10;
var b = 'hello';
module.exports = {age:a,name:b};

var a = {
name : 'hello',
age: 10
}
module.exports = a;

function a(){
console.log('hello')
}
module.exports = a;

var a = function (){
console.log('hello')
}
module.exports = a;

var a = {
name : 'hello',
getName : function(){
console.log(this.name)
}
}
module.exports = a;

exports

exports 是 module.exports 的一个引用,可以为 exports 添加属性,但不能直接赋值,那样就会失去对 module.exports 的引用,便不会暴露出所的值。

    exports.name = 'hello';

exports.getName = function(){
console.log(this.name)
}

因为是 module.exports 的引用,所以每次为 exports 赋值的时候,就是为 module.exports 赋值。 如果直接为 exports 赋值:

    exports = 'hello';

即改变了 exports 对 module.exports 的引用,所以所赋的值无法通过 module.exports 暴露出去。

exports = module.exports = xxx

可以在很多项目中看到这句,当模块要输出一个非 Object 时(比如一个 Function),可以使用

    module.exports = function () {}

此时 module.exports 被覆盖了,而 exports 还是原来的对像的引用,为了避免在后面的代码中仍然使用 exports.xx = yy 而导致不能正确输出,需要把 exports 变量也重新设置为新的 module.exports 的引用,所以一般习惯写成

    exports = module.exports = xxx

· 阅读需 7 分钟

关于上篇看到 InfoQ 和 cnblog 上的博文后,做了个简单的测试。

建立 global 目录和 test 目录以及启动的 index.js,global 中用 module.exports 导出一个只有 a 属性并且 a 属性的值为一个空对象的 json 对象。

module.exports = {
a : {}
};

然后在根目录的 index.js 中引入这个模块,只需要 require('./global')即可,由于默认会在包下找到 index.js 作为入口。

引入后打印出此对象,然后为 a 属性中的 json 对象追加一个键值对 a=111, 再打印此时,对象中的值已经与开始导出时不同,如果每次加载模块时都直接加载的话那么在其他模块加载肯定与当前不同。

根目录的 index.js 代码

var context = require('./global');
var test = require('./test');
console.log(context.a);
context.a['a'] = 111;
console.log(" 当前 context 内容 ");
console.log(context.a);
console.log(" 从其他模块引入的 global");
test.getContext();

test 包中的 index.js 代码

var context = require('../global');
exports.getContext = function(){
console.log(context.a);
};

执行后的结果,如下:

{}
当前 context 内容
{ a: 111 }
从其他模块引入的 global
{ a: 111 }

由此可见,我们在改变了 global 模块中的值的时候,后续在其他模块引用这个模块,它没有直接加载这个模块,而是从 require 的 cache 中读取的缓存的内容。

这样的话,我在之前文章 《nodejs 通讯在工作中的思考和改进》 中提到的全局变量的问题,可以通过封装一个 global 模块来实现全局变量了。

不过我突然怀疑了一下,如果在 test 模块中改变下 global 中的值,这边是否也改变了呢,修改了下 test 中的代码,如下:

var context = require('../global');
exports.getContext = function(){
console.log(context.a);
};
exports.changeContext = function(){
context.a['b'] = 222;
};

看看是否是引用的相同对象,虽然这理论上就是这样,不过亲手证实了比较放心。

根目录的 index 中如下:

var context = require('./global');
var test = require('./test');
console.log(" 初始值 ");
console.log(context.a);
context.a['a'] = 111;
console.log(" 当前 context 内容 ");
console.log(context.a);
console.log(" 从其他模块引入的 global");
test.getContext();
console.log(" 在 test 模块中改变 global 中 a 添加属性 b 值 ");
test.changeContext();
console.log(" 改变后的值 ");
console.log(context.a);

再看看结果:

初始值
{}
当前 context 内容
{ a: 111 }
从其他模块引入的 global
{ a: 111 }
在 test 模块中改变 global 中 a 添加属性 b 值
改变后的值
{ a: 111, b: 222 }

恩,答案令人满意,确实是在缓存中共享的对象,这样就可以放心的封装为 global 模块了(可能在项目中叫做 applicationContext 比较贴切)。

· 阅读需 5 分钟

由于嫌项目中用到的 global 对象不太好,所以想将全局变量封装到自定义的 global 模块中,记得模块是有闭包特性的。

但是我的疑问是,比如我的 global 变量是一个变的,我在某个 js 中引入模块,那么我在另一个地方引入是不是重新初始化了呢。

InfoQ 和博客园找到了些答案,记下笔记。

模块载入策略

Node.js 的模块分为两类,一类为原生(核心)模块,一类为文件模块。原生模块在 Node.js 源代码编译的时候编译进了二进制执行文件,加载的速度最快。另一类文件模块是动态加载的,加载速度比原生模块慢。但是 Node.js 对原生模块和文件模块都进行了缓存,于是在第二次 require 时,是不会有重复开销的。其中原生模块都被定义在 lib 这个目录下面,文件模块则不定性。

原文地址:http://www.infoq.com/cn/articles/nodejs-module-mechanism/

在《深入浅出 nodejs》有这样一段(有部分增减):

1、nodejs 引入模块分四个步骤

路径分析 文件定位 编译执行 加入内存 2、核心模块部分在 node 源代码的编译过程中就编译成了二级制文件,在 node 启动时就直接加载如内存,所以这部分模块引入时,前三步省略,直接加入。

3、nodejs 的模块加载和浏览器 js 加载一样都有缓存机制,不同的是,浏览器仅仅缓存文件,而 nodejs 缓存的是编译和执行后的对象(缓存内存)。

原文地址:http://www.cnblogs.com/tyhmj/p/3799039.html

等测试后再发表评论~

· 阅读需 20 分钟

这篇文章也算是 nodejs 通讯在工作中的思考和改进吧,记录和分享下期中的体会和思路。

从最先开始的一个光 Socket Server,到现在的 Socket+websocket,其中经过了不少的变化与调整。

阶段一

最开始,紧紧是一个用于接收远端设备传送数据的 Socket 服务器,在这个阶段的代码也是很乱的,基本上所有的实现是以流水账的形式来走了一遍,基本可以满足了现有的需求,由于数据是按照了环保局的 212 通信协议来约定发送的,也让解析的阻力减小了不少,但是从总体代码来看,问题还是挺多的。

稍微列举下我能想到的:

复用性

软件写出来的目的就是为了尽可能的复用,从而简化了工作,从这方面一想,噢,我这时候实现的数据解析基本就是根据当前解析的一些特定数据来实现的,那么问题来了。

如果在这基础之上,又加入了新的数据,怎么办?找目前的情况来看,绝壁是要额外添加实现一遍解析的过程,那肯定是不好的。

扩展性

就像我上面说到的,整个过程都是耦合度很高的,相互都不能剥离,一步接着一步,这样的话,以后想在现有的通讯之上进行扩展,那是相当的麻烦。

分布式(横向扩展)

可能很多设计的比较好的系统来说,他们的瓶颈可能只是服务器的硬件或者是环境因素导致,这个时候只是额外的加入节点就可以解决性能问题,如果是这样的话,那就是最好的。

就我目前的项目来说,都还没往这方面考虑。

阶段二

然后,我第一次比较大的改动是,首先将各个功能划分的更细,分成了一些所谓的“功能模块”出来,database、handler、receive、messagequeue 等等这些。

是的,确实清晰多了,看上去好像各有各的分工。

差不多这时候也就是到了第二个项目,仍然是采用 nodejs 作为通讯服务器,沿用之前的架子吧,这次优化了一些东东:

  1. 项目中能做成配置文件的东西尽量不直接硬编码到代码中。
  2. 加入了一些日志调试信息。
  3. 所有日志打印的一些调试信息都被封装到一个消息字典里,而且还很自大的取了个名字叫 lang_zh_CN,哈哈,就算没有国际化的水平也要有一颗国际化的心嘛(万一实现了呢)。
  4. 解析部分,我是根据了 212 协议中各个区段的特点大概的封装了一下将数据用正则表达式的方式直接就替换+组装转化成 JSON 字符串,我想这应该比之前先循环的切分,再一个个组装到对象中要来的快(总体来说就是个嵌套循环) ,我老早就想优化,只是因为之前那个版本原本是从 java 的原始版本复制成的 nodejs 版本,所以并未做很大的改动,另一方面也是项目催的急根本没时间考虑这些个因素,可能前面的项目中的通讯会成为以个历史遗留问题吧,可能以后在系统的设计上要考虑到将原先的系统接入进来的可能性了。

阶段三

恩,再有就是现在的通讯加入了另一方面的需求,就是在 J2EE 的 web 系统中通过调用通讯端的接口来实现反控现场设备,那么这就涉及到了 HTTP 这一块。

由于之前我自己嫌那个破 Socket Server 太坑爹,每次看有没有错误都要连到服务器去把日志翻出来看,所以我一不爽就给他加入了个 web server(基于 express),上面显示当前服务器的连接数啊、接收数据包成功 / 失败数啊、入库的成功 / 失败数啊、错误数据的数据包内容列表啊、还有个 highchart 画的曲线图包括了包速率和丢包率(其实灵感来自于阿里云的控制台)。

这个设备反控的实现跟上面的 webserver 还是有点关系的,我由于做了周期性的统计当前数据接收入库等情况,而且显示到页面上来,难道我要不停的去刷新查询?

我有这么勤快么,我自己都不信,然后我就想起来之前的一个模块叫 socket.io 的,咋一看名字还以为是 socket,其实啊,它是 websocket 实现(HTML5 中出现的双工通信协议),然后一看,居然 IE8 也有 ajax 的封装实现,不错,就它了,于是就愉快的推送起服务端的数据通信状态了。

回到设备反控,也就是在 J2EE 系统这边引用了我的 socket.io 的客户端 js 进行实时的交互,请求我的 websocket Server,然后我在内部找到对应的 TCP socket 客户端实例进行指令的拼装和发送,接收到回应及数据之后再实时的返回到 J2EE 的页面上。

这里面主要是用到了 nodejs 中的事件发射监听机制 EventEimtter(其实我也不知道搞个事件机制在这到底有用没用),也是怕可能有不同的实现,所以封装了个基本的 Listener 里面用的 event.on()、event.emit() 和 event.removeListener() 这三个基本的,其他的具体以 prototype+call 方式来继承他的操作并加以各自的实现。

哦对,在这过程中也是了解到了 socket.io 的频道的机制,在这里我是用它来广播当前选定的设备所进行的操作,但是它返回的数据只返回给当前控制它的客户端,为此,我还在这上面捣鼓了半天的锁机制,也算是个很简单的实现吧,根据返回数据包中有结束标志的包来释放,核心思想就是不让多人同时操作,但可以看到这台设备在进行什么操作。

我给经理打了个比喻,在一个房间里,有一台电脑(设备),同时只能有一个人在用(反控),其他人必须要等他用完了才能用,这期间就只能看着他用。

差不多现在就是这个程度,我还是不太满意现在的整体实现,可能有下面几个方面:

  1. 如果我需要不单一个端口也就是多个 server,加入其它业务的通信,那么这个怎么设计会将开发成本以及扩展性做到最优。
  2. 目前由于要让一些连接的缓存起来或者配置信息方便使用等,用到了不少全局的变量,然后分布在各个模块,并且各个模块间有一定的耦合性,这种问题要怎么解决。
  3. 再就是考虑到以后可能横向扩展成分布式,前期应该怎么来设计。

这几个问题是我想的比较多而且不是特别擅长的,希望如果有看到想和我交流或者大神有过设计经验的还望不吝赐教。

我的邮箱 yjk99@qq.com

· 阅读需 4 分钟

该模块提供了钩子,实现了 socket.io 认证。无需使用查询字符串发送凭据,这不是一个良好的安全习惯。

默认情况下它通过标记客户的未认证的并监听的认证事件。如果客户端提供了错误的凭据或不验证它就会断开。当服务器等待连接的客户端进行身份验证,也不会发射任何事件。

用法

要为 socket.io 连接设置身份验证,只需将服务器套接字和一个配置对象传给 socketio-auth:

    var io = require('socket.io').listen(app);

require('socketio-auth')(io, {
authenticate: authenticate,
postAuthenticate: postAuthenticate,
timeout: 1000
});

所支持的参数是:

authenticate:唯一需要的参数。这是一个函数,它接受客户端发送的数据,并调用一个回调表明,如果认证是成功的:

    function authenticate(data, callback) {
var username = data.username;
var password = data.password;

db.findUser('User', {username:username}, function(err, user) {
if (err || !user) return callback(new Error("User not found"));
return callback(null, user.password == password);
}
}

postAuthenticate:在客户端进行身份验证后才能调用的一个函数。对保持用户和客户端 socket 之间的联系是非常有用的:

    function postAuthenticate(socket, data) {
var username = data.username;

db.findUser('User', {username:username}, function(err, user) {
socket.client.user = user;
}
}

timeout:客户端断开连接之前进行身份验证等待的毫秒数。默认值是 1000。 客户端只需要确保在连接后进行验证:

    var socket = io.connect('http://localhost');
socket.on('connect', function(){
socket.emit('authentication', {client: "John", password: "secret"});
});

该服务器将发射的已认证事件,以确认认证。

作者 Github:socketio-auth