-
Notifications
You must be signed in to change notification settings - Fork 451
Handle connection refused and connection timeouts gracefully #574
Conversation
This prevents node crashes from Client.bind() and etc. We set a callback handler in the instance during bind() and send errors to it.
This way, other tests keep passing
Technically I'm curious why this wouldn't work to catch the errors: const client = ldapjs.createClient(...)
client.on('error', (err) => {
if (err.code === 'ECONNREFUSED') {
// handle connection refused (LDAP server not started)
}
}).on('connectError', (err) => {
// handle connection timeout (firewall problem)
})
client.bind(...) Niether of these are bind errors so it seems weird to handle them in the bind callback. |
@@ -1005,6 +1013,8 @@ Client.prototype.connect = function connect () { | |||
// Communicate the last-encountered error | |||
if (err instanceof ConnectionError) { | |||
self.emit('connectTimeout', err) | |||
} else if (err.code === 'ECONNREFUSED') { | |||
self.emit('connectRefused', err) | |||
} else { | |||
self.emit('error', err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since this only emits errors in connect
should this just be changed to emit 'connectError'?
Note: I believe this could be a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those errors already emit a connectError
event on
node-ldapjs/lib/client/client.js
Line 803 in 5afa677
self.emit('connectError', err) |
This will emit another (more specific) error event.
On one hand, this can be a breaking change for codes handling connection errors with the error
event. On the other hand, the callback for bind
will be called with the error so previously written code will still work (it will just call another error handling code).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I am okay with the breaking change here since it will be going into a new major version. I have stated that the intention is to not drastically alter the API, but some breaking changes are going to make it in.
@UziTech Thanks for the prompt feedback! <3 Granted, binding to the I don't find it particularly shocking to emit connection errors while logging into the server, for example that's what the mysql module will do. |
That is why I think we should make the connection refused error emit a There are also events for
I do think we should move the the code would have to change to: const client = ldapjs.createClient(...)
client.connect((err) => {
// handle connection errors
client.bind(...)
}) |
Next release will be a semver major. So some breaking changes are acceptable. I would like to keep them to a minimum, however. The node-ldapjs/test/client.test.js Lines 371 to 387 in 5a31aba
But that might be a bit drastic of a change. A Also, we definitely need documentation for the supported events -- #340 |
This current code already does this, it's just that it will emit another crash-causing Perhaps an acceptable compromise would be to move the connect method call in the bind if not yet called, so that it makes more sense to throw errors from there, but purists would still be able to catch connection errors by calling connect beforehand? This way, previously written code would still work |
Why not go further and include the connect and bind call in the search if not yet called? const client = ldapjs.createClient({username, password, ...});
client.search(...); |
I'm thinking we should leave ldapjs as a low level library and not try to do too much. What do you guys think about abolishing callback errors and just using event emitter for errors? I can't think of another library that uses event emitter and callbacks for errors. It seems like the confusion comes from having the event emitter and callbacks. Looking through the code I have found a few other places where it is confusing whether the error should be sent to the callback or the event emitter or both. node-ldapjs/lib/client/client.js Lines 933 to 936 in ad451ed
|
I think connection level events make sense being reported through the event emitter interface. Same with streaming parts of the API. But async methods that don’t deal with events should return callback style errors. I think most of this will shake out as we restructure the code into more maintainable chunks (files). |
Hello there, I agree that using callbacks and events for error handling is counter-intuitive, however I'd favor callback-style error handling for async methods as @jsumners suggested since it should make it easier to react to them in accordance with the context in which LDAPjs was called. For example, when building a web app, one would not want to avoid creating a client at every request and bind to events manually. If the callback gives an error object if something goes wrong, it would enable users to directly answer to this request const app = require('express')()
const ldap = ldapjs.createClient(...)
ldap.bind('dn=directory', ...)
app.get('/search', (req, res) => {
ldap.search(req.body.query, ..., ..., (data, err) => {
// I want any errors that occured during the search here (be it a bad request or a timeout, or whatever)
if(err) res.end('oops, something went wrong')
else res.json(data).end()
}
} Furthermore, if I were to bind to events in my request, I may catch events related to another request, which would be confusing. Or I would have to create a client at every request which would cause performance issues (lots of sockets). TL;DR: Callback-style errors, please (which is actually sort of what this PR does for connection error events while bind-ing). I don't think it's a good idea to call the
So as long as it's said next to the bind method documentation that for connecting anonymously all you have to do is call connect instead of bind, it should be fine. |
Please include a minimal reproducible example |
This pull request rebases the work from #433 to the next branch. It also adds tests to the two cases concerned by the PR:
I took this as a opportunity to dive deeper into the code to understand what was actually causing the error to be thrown, I hope the code is a little cleaner than a blind handling of all errors (which caused other tests to fail). The issue was a poor handling of connection errors in the bind method. Since it's the first async method, it is the one that should report connection issues to the server, which it did not. Another issue was the handling of connection refused errors that caused a general error in ldapjs. Sending the exception to the bind callback avoids crashing the whole node process.