Skip to content

Commit 6e48356

Browse files
authored
Merge pull request #453 from ali-ince/1.7-sni-on-seed-router
Fixes around initial discovery
2 parents 84b42d7 + 34051df commit 6e48356

33 files changed

+1111
-777
lines changed

src/v1/driver.js

+12-10
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,17 @@ class Driver {
6262
/**
6363
* You should not be calling this directly, instead use {@link driver}.
6464
* @constructor
65-
* @param {string} hostPort
65+
* @param {ServerAddress} address
6666
* @param {string} userAgent
6767
* @param {object} authToken
6868
* @param {object} config
6969
* @protected
7070
*/
71-
constructor(hostPort, userAgent, authToken = {}, config = {}) {
71+
constructor(address, userAgent, authToken = {}, config = {}) {
7272
sanitizeConfig(config);
7373

7474
this._id = idGenerator++;
75-
this._hostPort = hostPort;
75+
this._address = address;
7676
this._userAgent = userAgent;
7777
this._openConnections = {};
7878
this._authToken = authToken;
@@ -102,7 +102,7 @@ class Driver {
102102
* @protected
103103
*/
104104
_afterConstruction() {
105-
this._log.info(`Direct driver ${this._id} created for server address ${this._hostPort}`);
105+
this._log.info(`Direct driver ${this._id} created for server address ${this._address}`);
106106
}
107107

108108
/**
@@ -133,9 +133,9 @@ class Driver {
133133
* @return {Promise<Connection>} promise resolved with a new connection or rejected when failed to connect.
134134
* @access private
135135
*/
136-
_createConnection(hostPort, release) {
137-
const connection = Connection.create(hostPort, this._config, this._createConnectionErrorHandler(), this._log);
138-
connection._release = () => release(hostPort, connection);
136+
_createConnection(address, release) {
137+
const connection = Connection.create(address, this._config, this._createConnectionErrorHandler(), this._log);
138+
connection._release = () => release(address, connection);
139139
this._openConnections[connection.id] = connection;
140140

141141
return connection.connect(this._userAgent, this._authToken)
@@ -144,6 +144,8 @@ class Driver {
144144
// notify Driver.onError callback about connection initialization errors
145145
this.onError(error);
146146
}
147+
// let's destroy this connection
148+
this._destroyConnection(connection);
147149
// propagate the error because connection failed to connect / initialize
148150
throw error;
149151
});
@@ -206,8 +208,8 @@ class Driver {
206208
}
207209

208210
// Extension point
209-
_createConnectionProvider(hostPort, connectionPool, driverOnErrorCallback) {
210-
return new DirectConnectionProvider(hostPort, connectionPool, driverOnErrorCallback);
211+
_createConnectionProvider(address, connectionPool, driverOnErrorCallback) {
212+
return new DirectConnectionProvider(address, connectionPool, driverOnErrorCallback);
211213
}
212214

213215
// Extension point
@@ -218,7 +220,7 @@ class Driver {
218220
_getOrCreateConnectionProvider() {
219221
if (!this._connectionProvider) {
220222
const driverOnErrorCallback = this._driverOnErrorCallback.bind(this);
221-
this._connectionProvider = this._createConnectionProvider(this._hostPort, this._pool, driverOnErrorCallback);
223+
this._connectionProvider = this._createConnectionProvider(this._address, this._pool, driverOnErrorCallback);
222224
}
223225
return this._connectionProvider;
224226
}

src/v1/index.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import urlUtil from './internal/url-util';
3131
import HttpDriver from './internal/http/http-driver';
3232
import {isPoint, Point} from './spatial-types';
3333
import {Date, DateTime, Duration, isDate, isDateTime, isDuration, isLocalDateTime, isLocalTime, isTime, LocalDateTime, LocalTime, Time} from './temporal-types';
34+
import ServerAddress from './internal/server-address';
3435

3536
/**
3637
* @property {function(username: string, password: string, realm: ?string)} basic the function to create a
@@ -227,12 +228,12 @@ function driver(url, authToken, config = {}) {
227228
assertString(url, 'Bolt URL');
228229
const parsedUrl = urlUtil.parseDatabaseUrl(url);
229230
if (parsedUrl.scheme === 'bolt+routing') {
230-
return new RoutingDriver(parsedUrl.hostAndPort, parsedUrl.query, USER_AGENT, authToken, config);
231+
return new RoutingDriver(ServerAddress.fromUrl(parsedUrl.hostAndPort), parsedUrl.query, USER_AGENT, authToken, config);
231232
} else if (parsedUrl.scheme === 'bolt') {
232233
if (!isEmptyObjectOrNull(parsedUrl.query)) {
233234
throw new Error(`Parameters are not supported with scheme 'bolt'. Given URL: '${url}'`);
234235
}
235-
return new Driver(parsedUrl.hostAndPort, USER_AGENT, authToken, config);
236+
return new Driver(ServerAddress.fromUrl(parsedUrl.hostAndPort), USER_AGENT, authToken, config);
236237
} else if (parsedUrl.scheme === 'http' || parsedUrl.scheme === 'https') {
237238
return new HttpDriver(parsedUrl, USER_AGENT, authToken, config);
238239
} else {

src/v1/internal/browser/browser-channel.js

+11-12
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export default class WebSocketChannel {
4646
return;
4747
}
4848

49-
this._ws = createWebSocket(scheme, config.url);
49+
this._ws = createWebSocket(scheme, config.address);
5050
this._ws.binaryType = "arraybuffer";
5151

5252
let self = this;
@@ -169,13 +169,13 @@ export default class WebSocketChannel {
169169
}
170170
}
171171

172-
function createWebSocket(scheme, parsedUrl) {
173-
const url = scheme + '://' + parsedUrl.hostAndPort;
172+
function createWebSocket(scheme, address) {
173+
const url = scheme + '://' + address.asHostPort();
174174

175175
try {
176176
return new WebSocket(url);
177177
} catch (error) {
178-
if (isIPv6AddressIssueOnWindows(error, parsedUrl)) {
178+
if (isIPv6AddressIssueOnWindows(error, address)) {
179179

180180
// WebSocket in IE and Edge browsers on Windows do not support regular IPv6 address syntax because they contain ':'.
181181
// It's an invalid character for UNC (https://en.wikipedia.org/wiki/IPv6_address#Literal_IPv6_addresses_in_UNC_path_names)
@@ -190,34 +190,33 @@ function createWebSocket(scheme, parsedUrl) {
190190
// Creation of WebSocket with unconverted address results in SyntaxError without message or stacktrace.
191191
// That is why here we "catch" SyntaxError and rewrite IPv6 address if needed.
192192

193-
const windowsFriendlyUrl = asWindowsFriendlyIPv6Address(scheme, parsedUrl);
193+
const windowsFriendlyUrl = asWindowsFriendlyIPv6Address(scheme, address);
194194
return new WebSocket(windowsFriendlyUrl);
195195
} else {
196196
throw error;
197197
}
198198
}
199199
}
200200

201-
function isIPv6AddressIssueOnWindows(error, parsedUrl) {
202-
return error.name === 'SyntaxError' && isIPv6Address(parsedUrl);
201+
function isIPv6AddressIssueOnWindows(error, address) {
202+
return error.name === 'SyntaxError' && isIPv6Address(address.asHostPort());
203203
}
204204

205-
function isIPv6Address(parsedUrl) {
206-
const hostAndPort = parsedUrl.hostAndPort;
205+
function isIPv6Address(hostAndPort) {
207206
return hostAndPort.charAt(0) === '[' && hostAndPort.indexOf(']') !== -1;
208207
}
209208

210-
function asWindowsFriendlyIPv6Address(scheme, parsedUrl) {
209+
function asWindowsFriendlyIPv6Address(scheme, address) {
211210
// replace all ':' with '-'
212-
const hostWithoutColons = parsedUrl.host.replace(new RegExp(':', 'g'), '-');
211+
const hostWithoutColons = address.host().replace(new RegExp(':', 'g'), '-');
213212

214213
// replace '%' with 's' for link-local IPv6 address like 'fe80::1%lo0'
215214
const hostWithoutPercent = hostWithoutColons.replace('%', 's');
216215

217216
// append magic '.ipv6-literal.net' suffix
218217
const ipv6Host = hostWithoutPercent + '.ipv6-literal.net';
219218

220-
return `${scheme}://${ipv6Host}:${parsedUrl.port}`;
219+
return `${scheme}://${ipv6Host}:${address.port()}`;
221220
}
222221

223222
/**

src/v1/internal/channel-config.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ export default class ChannelConfig {
3131

3232
/**
3333
* @constructor
34-
* @param {Url} url the URL for the channel to connect to.
34+
* @param {ServerAddress} address the address for the channel to connect to.
3535
* @param {object} driverConfig the driver config provided by the user when driver is created.
3636
* @param {string} connectionErrorCode the default error code to use on connection errors.
3737
*/
38-
constructor(url, driverConfig, connectionErrorCode) {
39-
this.url = url;
38+
constructor(address, driverConfig, connectionErrorCode) {
39+
this.address = address;
4040
this.encrypted = extractEncrypted(driverConfig);
4141
this.trust = extractTrust(driverConfig);
4242
this.trustedCertificates = extractTrustedCertificates(driverConfig);

src/v1/internal/connection-error-handler.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ export default class ConnectionErrorHandler {
3838
/**
3939
* Handle and transform the error.
4040
* @param {Neo4jError} error the original error.
41-
* @param {string} hostPort the host and port of the connection where the error happened.
41+
* @param {ServerAddress} address the address of the connection where the error happened.
4242
* @return {Neo4jError} new error that should be propagated to the user.
4343
*/
44-
handleAndTransformError(error, hostPort) {
44+
handleAndTransformError(error, address) {
4545
if (isAvailabilityError(error)) {
46-
return this._handleUnavailability(error, hostPort);
46+
return this._handleUnavailability(error, address);
4747
}
4848
if (isFailureToWrite(error)) {
49-
return this._handleWriteFailure(error, hostPort);
49+
return this._handleWriteFailure(error, address);
5050
}
5151
return error;
5252
}

src/v1/internal/connection-providers.js

+21-7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Session from '../session';
2323
import RoutingTable from './routing-table';
2424
import Rediscovery from './rediscovery';
2525
import RoutingUtil from './routing-util';
26+
import { HostNameResolver } from './node';
2627

2728
const UNAUTHORIZED_ERROR_CODE = 'Neo.ClientError.Security.Unauthorized';
2829

@@ -45,32 +46,33 @@ class ConnectionProvider {
4546

4647
export class DirectConnectionProvider extends ConnectionProvider {
4748

48-
constructor(hostPort, connectionPool, driverOnErrorCallback) {
49+
constructor(address, connectionPool, driverOnErrorCallback) {
4950
super();
50-
this._hostPort = hostPort;
51+
this._address = address;
5152
this._connectionPool = connectionPool;
5253
this._driverOnErrorCallback = driverOnErrorCallback;
5354
}
5455

5556
acquireConnection(mode) {
56-
const connectionPromise = this._connectionPool.acquire(this._hostPort);
57+
const connectionPromise = this._connectionPool.acquire(this._address);
5758
return this._withAdditionalOnErrorCallback(connectionPromise, this._driverOnErrorCallback);
5859
}
5960
}
6061

6162
export class LoadBalancer extends ConnectionProvider {
6263

63-
constructor(hostPort, routingContext, connectionPool, loadBalancingStrategy, hostNameResolver, driverOnErrorCallback, log) {
64+
constructor(address, routingContext, connectionPool, loadBalancingStrategy, hostNameResolver, driverOnErrorCallback, log) {
6465
super();
65-
this._seedRouter = hostPort;
66+
this._seedRouter = address;
6667
this._routingTable = new RoutingTable([this._seedRouter]);
6768
this._rediscovery = new Rediscovery(new RoutingUtil(routingContext));
6869
this._connectionPool = connectionPool;
6970
this._driverOnErrorCallback = driverOnErrorCallback;
7071
this._loadBalancingStrategy = loadBalancingStrategy;
7172
this._hostNameResolver = hostNameResolver;
73+
this._dnsResolver = new HostNameResolver();
7274
this._log = log;
73-
this._useSeedRouter = false;
75+
this._useSeedRouter = true;
7476
}
7577

7678
acquireConnection(accessMode) {
@@ -173,14 +175,26 @@ export class LoadBalancer extends ConnectionProvider {
173175
}
174176

175177
_fetchRoutingTableUsingSeedRouter(seenRouters, seedRouter) {
176-
const resolvedAddresses = this._hostNameResolver.resolve(seedRouter);
178+
const resolvedAddresses = this._resolveSeedRouter(seedRouter);
177179
return resolvedAddresses.then(resolvedRouterAddresses => {
178180
// filter out all addresses that we've already tried
179181
const newAddresses = resolvedRouterAddresses.filter(address => seenRouters.indexOf(address) < 0);
180182
return this._fetchRoutingTable(newAddresses, null);
181183
});
182184
}
183185

186+
_resolveSeedRouter(seedRouter) {
187+
const customResolution = this._hostNameResolver.resolve(seedRouter);
188+
const dnsResolutions = customResolution.then(resolvedAddresses => {
189+
return Promise.all(resolvedAddresses.map(address => {
190+
return this._dnsResolver.resolve(address);
191+
}));
192+
});
193+
return dnsResolutions.then(results => {
194+
return [].concat.apply([], results);
195+
});
196+
}
197+
184198
_fetchRoutingTable(routerAddresses, routingTable) {
185199
return routerAddresses.reduce((refreshedTablePromise, currentRouter, currentIndex) => {
186200
return refreshedTablePromise.then(newRoutingTable => {

src/v1/internal/connection.js

+11-12
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ export default class Connection {
5050
* @constructor
5151
* @param {Channel} channel - channel with a 'write' function and a 'onmessage' callback property.
5252
* @param {ConnectionErrorHandler} errorHandler the error handler.
53-
* @param {string} hostPort - the hostname and port to connect to.
53+
* @param {ServerAddress} address - the server address to connect to.
5454
* @param {Logger} log - the configured logger.
5555
* @param {boolean} disableLosslessIntegers if this connection should convert all received integers to native JS numbers.
5656
*/
57-
constructor(channel, errorHandler, hostPort, log, disableLosslessIntegers = false) {
57+
constructor(channel, errorHandler, address, log, disableLosslessIntegers = false) {
5858
this.id = idGenerator++;
59-
this.hostPort = hostPort;
60-
this.server = {address: hostPort};
59+
this.address = address;
60+
this.server = { address: address.asHostPort() };
6161
this.creationTimestamp = Date.now();
6262
this._errorHandler = errorHandler;
6363
this._disableLosslessIntegers = disableLosslessIntegers;
@@ -81,22 +81,21 @@ export default class Connection {
8181
this._isBroken = false;
8282

8383
if (this._log.isDebugEnabled()) {
84-
this._log.debug(`${this} created towards ${hostPort}`);
84+
this._log.debug(`${this} created towards ${address}`);
8585
}
8686
}
8787

8888
/**
8989
* Crete new connection to the provided address. Returned connection is not connected.
90-
* @param {string} url - the Bolt endpoint to connect to.
90+
* @param {ServerAddress} address - the Bolt endpoint to connect to.
9191
* @param {object} config - this driver configuration.
9292
* @param {ConnectionErrorHandler} errorHandler - the error handler for connection errors.
9393
* @param {Logger} log - configured logger.
9494
* @return {Connection} - new connection.
9595
*/
96-
static create(url, config, errorHandler, log) {
97-
const parsedAddress = urlUtil.parseDatabaseUrl(url);
98-
const channelConfig = new ChannelConfig(parsedAddress, config, errorHandler.errorCode());
99-
return new Connection(new Channel(channelConfig), errorHandler, parsedAddress.hostAndPort, log, config.disableLosslessIntegers);
96+
static create(address, config, errorHandler, log) {
97+
const channelConfig = new ChannelConfig(address, config, errorHandler.errorCode());
98+
return new Connection(new Channel(channelConfig), errorHandler, address, log, config.disableLosslessIntegers);
10099
}
101100

102101
/**
@@ -217,7 +216,7 @@ export default class Connection {
217216
*/
218217
_handleFatalError(error) {
219218
this._isBroken = true;
220-
this._error = this._errorHandler.handleAndTransformError(error, this.hostPort);
219+
this._error = this._errorHandler.handleAndTransformError(error, this.address);
221220

222221
if (this._log.isErrorEnabled()) {
223222
this._log.error(`${this} experienced a fatal error ${JSON.stringify(this._error)}`);
@@ -267,7 +266,7 @@ export default class Connection {
267266
}
268267
try {
269268
const error = newError(payload.message, payload.code);
270-
this._currentFailure = this._errorHandler.handleAndTransformError(error, this.hostPort);
269+
this._currentFailure = this._errorHandler.handleAndTransformError(error, this.address);
271270
this._currentObserver.onError( this._currentFailure );
272271
} finally {
273272
this._updateCurrentObserver();

src/v1/internal/http/http-driver.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,18 @@
2020
import Driver from '../../driver';
2121
import HttpSession from './http-session';
2222
import HttpSessionTracker from './http-session-tracker';
23+
import ServerAddress from '../server-address';
2324

2425
export default class HttpDriver extends Driver {
2526

26-
constructor(hostPort, userAgent, token, config) {
27-
super(hostPort, userAgent, token, config);
27+
constructor(url, userAgent, token, config) {
28+
super(ServerAddress.fromUrl(url.hostAndPort), userAgent, token, config);
29+
this._url = url;
2830
this._sessionTracker = new HttpSessionTracker();
2931
}
3032

3133
session() {
32-
return new HttpSession(this._hostPort, this._authToken, this._config, this._sessionTracker);
34+
return new HttpSession(this._url, this._authToken, this._config, this._sessionTracker);
3335
}
3436

3537
close() {

0 commit comments

Comments
 (0)