From 652ac1b40a65c77ad08e66bc3172e96a43d8c3e1 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Thu, 30 Jun 2022 17:32:36 +0200 Subject: [PATCH 1/6] Introduce `neo4j.hasReachableServer` API This is an experimental API responsible for verifying if the given url is running a Neo4j Server. Co-Authored-by: Oskar Damkjaer --- .../connection-provider-direct.js | 7 ++++++ .../connection-provider-routing.js | 7 ++++++ packages/core/src/connection-provider.ts | 12 ++++++++++ packages/core/src/driver.ts | 13 +++++++++++ packages/neo4j-driver-lite/src/index.ts | 22 +++++++++++++++++++ .../test/integration/driver.test.ts | 8 +++++++ packages/neo4j-driver/src/index.js | 22 +++++++++++++++++++ packages/neo4j-driver/test/driver.test.js | 14 ++++++++++++ packages/neo4j-driver/types/index.d.ts | 7 ++++++ 9 files changed, 112 insertions(+) diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-direct.js b/packages/bolt-connection/src/connection-provider/connection-provider-direct.js index d93b0638b..b4cb1ce61 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-direct.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-direct.js @@ -92,6 +92,13 @@ export default class DirectConnectionProvider extends PooledConnectionProvider { ) } + getNegotiatedProtocolVersion () { + return new Promise((resolve, reject) => { + this._hasProtocolVersion(resolve) + .catch(reject) + }) + } + async supportsTransactionConfig () { return await this._hasProtocolVersion( version => version >= BOLT_PROTOCOL_V3 diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js index d8f573a0a..7e8e77790 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-routing.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-routing.js @@ -244,6 +244,13 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider ) } + getNegotiatedProtocolVersion () { + return new Promise((resolve, reject) => { + this._hasProtocolVersion(resolve) + .catch(reject) + }) + } + async verifyConnectivityAndGetServerInfo ({ database, accessMode }) { const context = { database: database || DEFAULT_DB_NAME } diff --git a/packages/core/src/connection-provider.ts b/packages/core/src/connection-provider.ts index a6b2bb53d..b0775683c 100644 --- a/packages/core/src/connection-provider.ts +++ b/packages/core/src/connection-provider.ts @@ -98,6 +98,18 @@ class ConnectionProvider { throw Error('Not implemented') } + /** + * Returns the protocol version negotiated via handshake. + * + * Note that this function call _always_ causes a round-trip to the server. + * + * @returns {Promise} the protocol version negotiated via handshake. + * @throws {Error} When protocol negotiation fails + */ + getNegotiatedProtocolVersion (): Promise { + throw Error('Not Implemented') + } + /** * Closes this connection provider along with its internals (connections, pools, etc.) * diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 9e2b7d641..241115e8d 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -220,6 +220,19 @@ class Driver { return connectionProvider.supportsUserImpersonation() } + /** + * Returns the protocol version negotiated via handshake. + * + * Note that this function call _always_ causes a round-trip to the server. + * + * @returns {Promise} the protocol version negotiated via handshake. + * @throws {Error} When protocol negotiation fails + */ + getNegotiatedProtocolVersion (): Promise { + const connectionProvider = this._getOrCreateConnectionProvider() + return connectionProvider.getNegotiatedProtocolVersion() + } + /** * Returns boolean to indicate if driver has been configured with encryption enabled. * diff --git a/packages/neo4j-driver-lite/src/index.ts b/packages/neo4j-driver-lite/src/index.ts index e961d2108..d0cdc9420 100644 --- a/packages/neo4j-driver-lite/src/index.ts +++ b/packages/neo4j-driver-lite/src/index.ts @@ -328,6 +328,26 @@ function driver ( } } +/** + * Method which veirfies if the driver is server is reachable. + * + * @experimental + * @since 5.0.0 + * @param {string} url The URL for the Neo4j database, for instance "neo4j://localhost" and/or "bolt://localhost" + * @param {Pick} config Configuration object. See the {@link driver} + * @returns {true} When the server is reachable + * @throws {Error} When the server is not reacheable or the url is invalid + */ +async function hasReachableServer (url: string, config?: Pick): Promise { + const nonLoggedDriver = driver(url, { scheme: 'none', principal: '', credentials: '' }, config) + try { + await nonLoggedDriver.getNegotiatedProtocolVersion() + return true + } finally { + await nonLoggedDriver.close() + } +} + const USER_AGENT: string = 'neo4j-javascript/' + VERSION /** @@ -393,6 +413,7 @@ const temporal = { */ const forExport = { driver, + hasReachableServer, int, isInt, isPoint, @@ -444,6 +465,7 @@ const forExport = { export { driver, + hasReachableServer, int, isInt, isPoint, diff --git a/packages/neo4j-driver-lite/test/integration/driver.test.ts b/packages/neo4j-driver-lite/test/integration/driver.test.ts index 3bac73513..1bd4b167c 100644 --- a/packages/neo4j-driver-lite/test/integration/driver.test.ts +++ b/packages/neo4j-driver-lite/test/integration/driver.test.ts @@ -45,4 +45,12 @@ describe('neo4j-driver-lite', () => { expect(result.records[0].length).toEqual(1) result.records[0].forEach(val => expect(val).toEqual(int(2))) }) + + test('hasReachableServer success', async () => { + await expect(neo4j.hasReachableServer(`${scheme}://${hostname}`)).resolves.toBe(true) + }) + + test('hasReachableServer failure', async () => { + await expect(neo4j.hasReachableServer(`${scheme}://${hostname}:9999`)).rejects.toBeInstanceOf(Error) + }) }) diff --git a/packages/neo4j-driver/src/index.js b/packages/neo4j-driver/src/index.js index cafef42f0..e7996b3a1 100644 --- a/packages/neo4j-driver/src/index.js +++ b/packages/neo4j-driver/src/index.js @@ -305,6 +305,26 @@ function driver (url, authToken, config = {}) { } } +/** + * Method which veirfies if the driver is server is reachable. + * + * @experimental + * @since 5.0.0 + * @param {string} url The URL for the Neo4j database, for instance "neo4j://localhost" and/or "bolt://localhost" + * @param {object} config Configuration object. See the {@link driver} + * @returns {true} When the server is reachable + * @throws {Error} When the server is not reacheable or the url is invalid + */ +async function hasReachableServer (url, config) { + const nonLoggedDriver = driver(url, { scheme: 'none', principal: '', credentials: '' }, config) + try { + await nonLoggedDriver.getNegotiatedProtocolVersion() + return true + } finally { + await nonLoggedDriver.close() + } +} + const USER_AGENT = 'neo4j-javascript/' + VERSION /** @@ -385,6 +405,7 @@ const temporal = { */ const forExport = { driver, + hasReachableServer, int, isInt, isPoint, @@ -437,6 +458,7 @@ const forExport = { export { driver, + hasReachableServer, int, isInt, isPoint, diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index f4b7abaa5..1581027ae 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -588,6 +588,20 @@ describe('#integration driver', () => { }) }) + it('hasReachableServer success', async () => { + const result = neo4j.hasReachableServer(`${sharedNeo4j.scheme}://${sharedNeo4j.hostname}`) + expect(result).toBe(true) + }) + + it('hasReachableServer failure', async () => { + try { + await neo4j.hasReachableServer(`${sharedNeo4j.scheme}://${sharedNeo4j.hostname}:9999`) + expect(true).toBe('false') + } catch (error) { + expect(error).toBeInstanceOf(Error) + } + }) + const integersWithNativeNumberEquivalent = [ [neo4j.int(0), 0], [neo4j.int(42), 42], diff --git a/packages/neo4j-driver/types/index.d.ts b/packages/neo4j-driver/types/index.d.ts index 872c94817..89937a80e 100644 --- a/packages/neo4j-driver/types/index.d.ts +++ b/packages/neo4j-driver/types/index.d.ts @@ -100,6 +100,11 @@ declare function driver ( config?: Config ): Driver +declare function hasReachableServer ( + url: string, + config?: Pick +): Promise + declare const types: { Node: typeof Node Relationship: typeof Relationship @@ -158,6 +163,7 @@ declare const temporal: { declare const forExport: { driver: typeof driver + hasReachableServer: typeof hasReachableServer int: typeof int isInt: typeof isInt integer: typeof integer @@ -218,6 +224,7 @@ declare const forExport: { export { driver, + hasReachableServer, int, isInt, integer, From 90de99705e2b25bf93709cd033ee0c41cc0a6111 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Thu, 30 Jun 2022 18:27:27 +0200 Subject: [PATCH 2/6] Fixing test --- packages/neo4j-driver-lite/test/unit/index.test.ts | 3 ++- packages/neo4j-driver/test/driver.test.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/neo4j-driver-lite/test/unit/index.test.ts b/packages/neo4j-driver-lite/test/unit/index.test.ts index 3d19fe85c..9899a1e16 100644 --- a/packages/neo4j-driver-lite/test/unit/index.test.ts +++ b/packages/neo4j-driver-lite/test/unit/index.test.ts @@ -222,7 +222,8 @@ describe('index', () => { supportsMultiDb: async () => true, supportsTransactionConfig: async () => true, supportsUserImpersonation: async () => true, - verifyConnectivityAndGetServerInfo: async () => new ServerInfo({}) + verifyConnectivityAndGetServerInfo: async () => new ServerInfo({}), + getNegotiatedProtocolVersion: async () => 5.0 } }) expect(session).toBeDefined() diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 1581027ae..5ec91937c 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -589,7 +589,7 @@ describe('#integration driver', () => { }) it('hasReachableServer success', async () => { - const result = neo4j.hasReachableServer(`${sharedNeo4j.scheme}://${sharedNeo4j.hostname}`) + const result = await neo4j.hasReachableServer(`${sharedNeo4j.scheme}://${sharedNeo4j.hostname}`) expect(result).toBe(true) }) From 02de12c027f41907518c0102a901043963b12934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Barc=C3=A9los?= Date: Fri, 1 Jul 2022 15:54:05 +0200 Subject: [PATCH 3/6] Apply suggestions from code review Co-authored-by: Oskar Damkjaer --- packages/neo4j-driver-lite/src/index.ts | 4 ++-- packages/neo4j-driver/src/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/neo4j-driver-lite/src/index.ts b/packages/neo4j-driver-lite/src/index.ts index d0cdc9420..3456b190c 100644 --- a/packages/neo4j-driver-lite/src/index.ts +++ b/packages/neo4j-driver-lite/src/index.ts @@ -329,14 +329,14 @@ function driver ( } /** - * Method which veirfies if the driver is server is reachable. + * Verifies if the driver can reach a server at the given url. * * @experimental * @since 5.0.0 * @param {string} url The URL for the Neo4j database, for instance "neo4j://localhost" and/or "bolt://localhost" * @param {Pick} config Configuration object. See the {@link driver} * @returns {true} When the server is reachable - * @throws {Error} When the server is not reacheable or the url is invalid + * @throws {Error} When the server is not reachable or the url is invalid */ async function hasReachableServer (url: string, config?: Pick): Promise { const nonLoggedDriver = driver(url, { scheme: 'none', principal: '', credentials: '' }, config) diff --git a/packages/neo4j-driver/src/index.js b/packages/neo4j-driver/src/index.js index e7996b3a1..338531484 100644 --- a/packages/neo4j-driver/src/index.js +++ b/packages/neo4j-driver/src/index.js @@ -313,7 +313,7 @@ function driver (url, authToken, config = {}) { * @param {string} url The URL for the Neo4j database, for instance "neo4j://localhost" and/or "bolt://localhost" * @param {object} config Configuration object. See the {@link driver} * @returns {true} When the server is reachable - * @throws {Error} When the server is not reacheable or the url is invalid + * @throws {Error} When the server is not reachable or the url is invalid */ async function hasReachableServer (url, config) { const nonLoggedDriver = driver(url, { scheme: 'none', principal: '', credentials: '' }, config) From f5fc7a43669e87ea69e52d3e66546de2218b6d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Barc=C3=A9los?= Date: Fri, 1 Jul 2022 10:56:48 -0300 Subject: [PATCH 4/6] Address code review suggestions --- packages/neo4j-driver/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/neo4j-driver/src/index.js b/packages/neo4j-driver/src/index.js index 338531484..d706e5add 100644 --- a/packages/neo4j-driver/src/index.js +++ b/packages/neo4j-driver/src/index.js @@ -306,7 +306,7 @@ function driver (url, authToken, config = {}) { } /** - * Method which veirfies if the driver is server is reachable. + * Verifies if the driver can reach a server at the given url. * * @experimental * @since 5.0.0 From f4bf000367b8255df5e0ac0ccbdbe4fccf74ad37 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Mon, 4 Jul 2022 14:02:41 +0200 Subject: [PATCH 5/6] Fix test --- packages/neo4j-driver/test/driver.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index 5ec91937c..abf13fb09 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -594,12 +594,14 @@ describe('#integration driver', () => { }) it('hasReachableServer failure', async () => { + let success = false try { await neo4j.hasReachableServer(`${sharedNeo4j.scheme}://${sharedNeo4j.hostname}:9999`) - expect(true).toBe('false') + success = true } catch (error) { expect(error).toBeInstanceOf(Error) } + expect(success).toEqual(false) }) const integersWithNativeNumberEquivalent = [ From 80065af4f0d8eb2ebba7554235e8246600d20491 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Mon, 4 Jul 2022 14:20:03 +0200 Subject: [PATCH 6/6] Adjusting test --- packages/neo4j-driver/test/driver.test.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/neo4j-driver/test/driver.test.js b/packages/neo4j-driver/test/driver.test.js index abf13fb09..1883a541c 100644 --- a/packages/neo4j-driver/test/driver.test.js +++ b/packages/neo4j-driver/test/driver.test.js @@ -589,19 +589,13 @@ describe('#integration driver', () => { }) it('hasReachableServer success', async () => { - const result = await neo4j.hasReachableServer(`${sharedNeo4j.scheme}://${sharedNeo4j.hostname}`) - expect(result).toBe(true) + await expectAsync(neo4j.hasReachableServer(`${sharedNeo4j.scheme}://${sharedNeo4j.hostname}`)) + .toBeResolvedTo(true) }) it('hasReachableServer failure', async () => { - let success = false - try { - await neo4j.hasReachableServer(`${sharedNeo4j.scheme}://${sharedNeo4j.hostname}:9999`) - success = true - } catch (error) { - expect(error).toBeInstanceOf(Error) - } - expect(success).toEqual(false) + await expectAsync(neo4j.hasReachableServer(`${sharedNeo4j.scheme}://${sharedNeo4j.hostname}:9999`)) + .toBeRejected() }) const integersWithNativeNumberEquivalent = [