Skip to content

Commit

Permalink
feat: add redirect info to connection errors (#1527)
Browse files Browse the repository at this point in the history
Co-authored with @arthurschreiber
Co-authored with @mShan0
  • Loading branch information
MichaelSun90 authored Jul 20, 2023
1 parent c02e30f commit b78df14
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 3 deletions.
18 changes: 16 additions & 2 deletions src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2129,7 +2129,14 @@ class Connection extends EventEmitter {
* @private
*/
connectTimeout() {
const message = `Failed to connect to ${this.config.server}${this.config.options.port ? `:${this.config.options.port}` : `\\${this.config.options.instanceName}`} in ${this.config.options.connectTimeout}ms`;
const hostPostfix = this.config.options.port ? `:${this.config.options.port}` : `\\${this.config.options.instanceName}`;
// If we have routing data stored, this connection has been redirected
const server = this.routingData ? this.routingData.server : this.config.server;
const port = this.routingData ? `:${this.routingData.port}` : hostPostfix;
// Grab the target host from the connection configration, and from a redirect message
// otherwise, leave the message empty.
const routingMessage = this.routingData ? ` (redirected from ${this.config.server}${hostPostfix})` : '';
const message = `Failed to connect to ${server}${port}${routingMessage} in ${this.config.options.connectTimeout}ms`;
this.debug.log(message);
this.emit('connect', new ConnectionError(message, 'ETIMEOUT'));
this.connectTimer = undefined;
Expand Down Expand Up @@ -2258,7 +2265,14 @@ class Connection extends EventEmitter {
*/
socketError(error: Error) {
if (this.state === this.STATE.CONNECTING || this.state === this.STATE.SENT_TLSSSLNEGOTIATION) {
const message = `Failed to connect to ${this.config.server}:${this.config.options.port} - ${error.message}`;
const hostPostfix = this.config.options.port ? `:${this.config.options.port}` : `\\${this.config.options.instanceName}`;
// If we have routing data stored, this connection has been redirected
const server = this.routingData ? this.routingData.server : this.config.server;
const port = this.routingData ? `:${this.routingData.port}` : hostPostfix;
// Grab the target host from the connection configration, and from a redirect message
// otherwise, leave the message empty.
const routingMessage = this.routingData ? ` (redirected from ${this.config.server}${hostPostfix})` : '';
const message = `Failed to connect to ${server}${port}${routingMessage} - ${error.message}`;
this.debug.log(message);
this.emit('connect', new ConnectionError(message, 'ESOCKET'));
} else {
Expand Down
83 changes: 82 additions & 1 deletion test/unit/rerouting-test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { assert } = require('chai');
const net = require('net');

const { Connection } = require('../../src/tedious');
const { Connection, ConnectionError } = require('../../src/tedious');
const IncomingMessageStream = require('../../src/incoming-message-stream');
const OutgoingMessageStream = require('../../src/outgoing-message-stream');
const Debug = require('../../src/debug');
Expand Down Expand Up @@ -381,4 +381,85 @@ describe('Connecting to a server that sends a re-routing information', function(
connection.close();
}
});

it('it should throw an error with redirect information when targetserver connection failed', async function() {
routingServer.on('connection', async (connection) => {
const debug = new Debug();
const incomingMessageStream = new IncomingMessageStream(debug);
const outgoingMessageStream = new OutgoingMessageStream(debug, { packetSize: 4 * 1024 });

connection.pipe(incomingMessageStream);
outgoingMessageStream.pipe(connection);

try {
const messageIterator = incomingMessageStream[Symbol.asyncIterator]();

// PRELOGIN
{
const { value: message } = await messageIterator.next();
assert.strictEqual(message.type, 0x12);

const chunks = [];
for await (const data of message) {
chunks.push(data);
}

const responsePayload = new PreloginPayload({ encrypt: false, version: { major: 0, minor: 0, build: 0, subbuild: 0 } });
const responseMessage = new Message({ type: 0x12 });
responseMessage.end(responsePayload.data);
outgoingMessageStream.write(responseMessage);
}

// LOGIN7
{
const { value: message } = await messageIterator.next();
assert.strictEqual(message.type, 0x10);

const chunks = [];
for await (const data of message) {
chunks.push(data);
}

const responseMessage = new Message({ type: 0x04 });
responseMessage.write(buildLoginAckToken());
responseMessage.end(buildRoutingEnvChangeToken('test.invalid', targetServer.address().port));
outgoingMessageStream.write(responseMessage);
}

// No further messages, connection closed on remote
{
const { done } = await messageIterator.next();
assert.isTrue(done);
}
} catch (err) {
process.nextTick(() => {
throw err;
});
} finally {
connection.end();
}
});

const connection = new Connection({
server: routingServer.address().address,
options: {
port: routingServer.address().port,
encrypt: false
}
});

try {
await new Promise((resolve, reject) => {
connection.connect((err) => {
err ? reject(err) : resolve(err);
});
});
} catch (err) {
assert.instanceOf(err, ConnectionError);
const message = `Failed to connect to test.invalid:${targetServer.address().port} (redirected from ${routingServer.address().address}:${routingServer.address().port})`;
assert.include(err.message, message);
} finally {
connection.close();
}
});
});

0 comments on commit b78df14

Please sign in to comment.