ECONNRESET exception nodes ldapjs

慕佑运
2023-12-01

nodejs: v8.11
ldapjs: v1.0.2

  • 背景
    Node.js抛异常ECONNRESET退出

// 看到网上有下面的例子程序,顺便拿来借用(多谢原作者),因为项目调用ldapjs的方式类似于下面的思路
http://blog.cuicc.com/blog/2017/03/26/nodejs-ECONNRESET/

var express = require('express');
var ldap = require('ldapjs');
...
var app = express();
app.post(
    'auth',
    function(req, res){
        var user = {
            username: req.body.username,
            password: req.body.password,
            dn      : ''
        }
        var adminClient = ldap.creatClient({url:url});
        adminClient.bind(admin.dn, admin.password, function(err){
            assert.ifError(err);
            var opts = {
                filter: '(&(objectclass=user)(samaccountname=' + userId + '))',
                scope: 'sub'
            };
            adminClient.search(base, opts, function(err, search){
                search.on('searchEntry', function(entry){
                    user.dn = entry.dn; 

                });
                search.on('end', function(err){
                    if(user.dn !== ''){
                        adminClient.unbind(err => {
                        });
                        var userClient = ldap.createClient({url:url});
                        userClient.bind(user.dn, user.password, function(err){
                            userClient.unbind(err => {
                            });
                            if(err){
                                res.json({'status':2, 'message':'invalid password'});
                            }
                            res.json({'status':0, 'message':'auth ok'});
                        });
                    } else {
                        res.json({'status': 1, 'message': 'invalid user'});
                    }
                });
            });
        });
    }
);
app.listen(8080, function(){
    console.log('listening on port 8080');
});

项目中用到类似上面例子的ldap验证步骤做登录验证,
正常情况下,如果ldap server端,为用户做的配置是类似下面的全英文配置,是可以直接一次bind(mayun, password…)验证的
“dn”:”CN=mayun,OU=alipay,OU=Employee,OU=DEVELOP,DC=develop,DC=alipay,DC=local”

但是有时ldap server端,为用户做的配置是类似下面的全角字符或汉字,
“dn”:”CN=马云,OU=alipay,OU=Employee,OU=DEVELOP,DC=develop,DC=alipay,DC=local”
能想到的一个办法就是先申请个类似于admin_user/admin_pw的用户,配置到后台的配置文件中,先用这个admin做bind(admin_user, admin_pw…),
之后search()查找出用户userId的真正的entry.dn,
之后userClient.bind(user.dn, user.password)去做用户的ldap认证服务。验证成功后,程序继续处理正常的业务逻辑。

但是ldapjs client端的连接要正确处理(比如释放连接connection),否则可能会抛出ECONNRESET的奇怪异常,

uncaughtException err--> Error: read ECONNRESET, err.stack-->
Error: read ECONNRESET
at _errnoException (util.js:1022:11)
at TCP.onread (net.js:628:25)

第一次在nodejs中看到这个异常,没有想到会是ldapjs抛出的异常,跟踪了好长时间,最终确定是ldapjs client的异常,

node –abort-on-uncaught-exception –track-heap-objects –trace_gc –trace_gc_nvp –trace_gc_verbose nodeserver.js

这里跟踪这个异常的过程有些曲折,本想查看nodejs的GC日志,内存占用等,但是并没有找出特别有用的信息,stackoverflow也有ECONNRESET的问题,但一直都以为是nodejs或者mongodb或者业务逻辑的程序的某个地方抛出的这个未捕获的异常,后来在开发环境上加上–abort-on-uncaught-exception后,当产生异常时,直接中断,并打印堆栈,不会抛出到uncaught去捕获(如果在uncaught捕获时,只能看到缩减版的堆栈信息,看不到是哪里抛出的异常), 这时直接打印日志看到是ldap.js的程序,才恍然大悟是ldapjs的问题。
stackoverflow有一篇文章ldapjs - LDAP connection error handling,解释如下:

ECONNRESET error occurs when after some idle time, the connection to LDAP service is reset. This is a common problem since a prolonged connection to ldap cannot be maintained
注: 可以看出ldap客户端到ldap服务端的connection,因为在server端不能维持一个持续很久的连接,所以其在server端idle时,可能就断开了,这时client端可能还需要请求这个已经存在的连接,也可能类似于心跳的服务,但是server端的connection已经关掉,这时会抛出ECONNRESET的异常

因为ldapjs官网及文档并没有明确给出如何正常处理client连接connection的问题,后来经过自己测试,当adminClient和userClient用完之后,直接调用 .unbind去断开连接,这样程序就不会抛出ECONNRESET的异常了,

ldapjs的维护者pfmooney说:

unbind. (It will close the socket on your behalf after sending a message to the server)
注: 因为ldapjs也有个destroy方法,经过测试,这里释放connction时,把unbind改为destroy也可以。另外ldapjs的源码中destroy调用了unbind。
个人理解如果调用ldapjs时用到连接池的话,如果需要释放连接可能用unbind是正确的选择,因为用destroy释放后,Calling destroy will prevent any further reconnection from occurring,不允许重新连接。

还有一个比较重要的点需要注意:
有些文档中说可以ldapclient.on(‘error’…),加这个监听是正确的,
但是,不要在监听的回调函数里在调用destroy方法,destroy方法里也可能会再次emit触发error的监听,这样on error再次接收,之后再次destroy,形成死循环,耗尽cpu和内存

adminClient.on('error', err => {
    // adminClient.destroy(); 这里不需要加destroy函数
});

ldapjs参考文档
http://ldapjs.org/examples.html
https://npmdoc.github.io/node-npmdoc-ldapjs/build/apidoc.html#apidoc.element.ldapjs.Client.prototype.unbind
https://stackoverflow.com/questions/41446941/ldapjs-ldap-connection-error-handling
https://github.com/joyent/node-ldapjs/issues/375
https://github.com/joyent/node-ldapjs/issues/317
https://github.com/joyent/node-ldapjs/issues/318
https://github.com/joyent/node-ldapjs/issues/142

 类似资料:

相关阅读

相关文章

相关问答