Skip to content

Commit 04379dd

Browse files
authored
Merge pull request #364 from lutovich/1.6-port-80
Fix handling of bolt URL with port 80
2 parents b97fc8a + 56ff62c commit 04379dd

11 files changed

+117
-72
lines changed

package-lock.json

+17-15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,6 @@
7373
},
7474
"dependencies": {
7575
"babel-runtime": "^6.18.0",
76-
"url-parse": "^1.2.0"
76+
"uri-js": "^4.2.1"
7777
}
7878
}

src/v1/driver.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,16 @@ class Driver {
5656
/**
5757
* You should not be calling this directly, instead use {@link driver}.
5858
* @constructor
59-
* @param {string} url
59+
* @param {string} hostPort
6060
* @param {string} userAgent
6161
* @param {object} token
6262
* @param {object} config
6363
* @protected
6464
*/
65-
constructor(url, userAgent, token = {}, config = {}) {
65+
constructor(hostPort, userAgent, token = {}, config = {}) {
6666
sanitizeConfig(config);
6767

68-
this._url = url;
68+
this._hostPort = hostPort;
6969
this._userAgent = userAgent;
7070
this._openSessions = {};
7171
this._sessionIdGenerator = 0;
@@ -116,13 +116,13 @@ class Driver {
116116
* @return {Connection} new connector-api session instance, a low level session API.
117117
* @access private
118118
*/
119-
_createConnection(url, release) {
119+
_createConnection(hostPort, release) {
120120
let sessionId = this._sessionIdGenerator++;
121-
let conn = connect(url, this._config, this._connectionErrorCode());
121+
let conn = connect(hostPort, this._config, this._connectionErrorCode());
122122
let streamObserver = new _ConnectionStreamObserver(this, conn);
123123
conn.initialize(this._userAgent, this._token, streamObserver);
124124
conn._id = sessionId;
125-
conn._release = () => release(url, conn);
125+
conn._release = () => release(hostPort, conn);
126126

127127
this._openSessions[sessionId] = conn;
128128
return conn;
@@ -185,8 +185,8 @@ class Driver {
185185
}
186186

187187
// Extension point
188-
_createConnectionProvider(address, connectionPool, driverOnErrorCallback) {
189-
return new DirectConnectionProvider(address, connectionPool, driverOnErrorCallback);
188+
_createConnectionProvider(hostPort, connectionPool, driverOnErrorCallback) {
189+
return new DirectConnectionProvider(hostPort, connectionPool, driverOnErrorCallback);
190190
}
191191

192192
// Extension point
@@ -203,7 +203,7 @@ class Driver {
203203
_getOrCreateConnectionProvider() {
204204
if (!this._connectionProvider) {
205205
const driverOnErrorCallback = this._driverOnErrorCallback.bind(this);
206-
this._connectionProvider = this._createConnectionProvider(this._url, this._pool, driverOnErrorCallback);
206+
this._connectionProvider = this._createConnectionProvider(this._hostPort, this._pool, driverOnErrorCallback);
207207
}
208208
return this._connectionProvider;
209209
}

src/v1/internal/connection-providers.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -44,24 +44,24 @@ class ConnectionProvider {
4444

4545
export class DirectConnectionProvider extends ConnectionProvider {
4646

47-
constructor(address, connectionPool, driverOnErrorCallback) {
47+
constructor(hostPort, connectionPool, driverOnErrorCallback) {
4848
super();
49-
this._address = address;
49+
this._hostPort = hostPort;
5050
this._connectionPool = connectionPool;
5151
this._driverOnErrorCallback = driverOnErrorCallback;
5252
}
5353

5454
acquireConnection(mode) {
55-
const connectionPromise = this._connectionPool.acquire(this._address);
55+
const connectionPromise = this._connectionPool.acquire(this._hostPort);
5656
return this._withAdditionalOnErrorCallback(connectionPromise, this._driverOnErrorCallback);
5757
}
5858
}
5959

6060
export class LoadBalancer extends ConnectionProvider {
6161

62-
constructor(address, routingContext, connectionPool, loadBalancingStrategy, driverOnErrorCallback) {
62+
constructor(hostPort, routingContext, connectionPool, loadBalancingStrategy, driverOnErrorCallback) {
6363
super();
64-
this._seedRouter = address;
64+
this._seedRouter = hostPort;
6565
this._routingTable = new RoutingTable([this._seedRouter]);
6666
this._rediscovery = new Rediscovery(new RoutingUtil(routingContext));
6767
this._connectionPool = connectionPool;

src/v1/internal/connector.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -96,17 +96,17 @@ class Connection {
9696
/**
9797
* @constructor
9898
* @param {NodeChannel|WebSocketChannel} channel - channel with a 'write' function and a 'onmessage' callback property.
99-
* @param {string} url - the hostname and port to connect to.
99+
* @param {string} hostPort - the hostname and port to connect to.
100100
* @param {boolean} disableLosslessIntegers if this connection should convert all received integers to native JS numbers.
101101
*/
102-
constructor(channel, url, disableLosslessIntegers = false) {
102+
constructor(channel, hostPort, disableLosslessIntegers = false) {
103103
/**
104104
* An ordered queue of observers, each exchange response (zero or more
105105
* RECORD messages followed by a SUCCESS message) we receive will be routed
106106
* to the next pending observer.
107107
*/
108-
this.url = url;
109-
this.server = {address: url};
108+
this.hostPort = hostPort;
109+
this.server = {address: hostPort};
110110
this.creationTimestamp = Date.now();
111111
this._disableLosslessIntegers = disableLosslessIntegers;
112112
this._pendingObservers = [];
@@ -516,18 +516,18 @@ class ConnectionState {
516516
}
517517

518518
/**
519-
* Crete new connection to the provided url.
519+
* Crete new connection to the provided address.
520520
* @access private
521-
* @param {string} url - 'neo4j'-prefixed URL to Neo4j Bolt endpoint
521+
* @param {string} hostPort - the Bolt endpoint to connect to
522522
* @param {object} config - this driver configuration
523523
* @param {string=null} connectionErrorCode - error code for errors raised on connection errors
524524
* @return {Connection} - New connection
525525
*/
526-
function connect(url, config = {}, connectionErrorCode = null) {
526+
function connect(hostPort, config = {}, connectionErrorCode = null) {
527527
const Ch = config.channel || Channel;
528-
const parsedUrl = urlUtil.parseDatabaseUrl(url);
529-
const channelConfig = new ChannelConfig(parsedUrl, config, connectionErrorCode);
530-
return new Connection(new Ch(channelConfig), parsedUrl.hostAndPort, config.disableLosslessIntegers);
528+
const parsedAddress = urlUtil.parseDatabaseUrl(hostPort);
529+
const channelConfig = new ChannelConfig(parsedAddress, config, connectionErrorCode);
530+
return new Connection(new Ch(channelConfig), parsedAddress.hostAndPort, config.disableLosslessIntegers);
531531
}
532532

533533
export {

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ import HttpSessionTracker from './http-session-tracker';
2222

2323
export default class HttpDriver extends Driver {
2424

25-
constructor(url, userAgent, token, config) {
26-
super(url, userAgent, token, config);
25+
constructor(hostPort, userAgent, token, config) {
26+
super(hostPort, userAgent, token, config);
2727
this._sessionTracker = new HttpSessionTracker();
2828
}
2929

3030
session() {
31-
return new HttpSession(this._url, this._token, this._config, this._sessionTracker);
31+
return new HttpSession(this._hostPort, this._token, this._config, this._sessionTracker);
3232
}
3333

3434
close() {

src/v1/internal/url-util.js

+14-19
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* limitations under the License.
1717
*/
1818

19-
import ParsedUrl from 'url-parse';
19+
import {parse as uriJsParse} from 'uri-js';
2020
import {assertString} from './util';
2121

2222
const DEFAULT_BOLT_PORT = 7687;
@@ -68,14 +68,14 @@ function parseDatabaseUrl(url) {
6868
assertString(url, 'URL');
6969

7070
const sanitized = sanitizeUrl(url);
71-
const parsedUrl = new ParsedUrl(sanitized.url, {}, query => extractQuery(query, url));
71+
const parsedUrl = uriJsParse(sanitized.url);
7272

73-
const scheme = sanitized.schemeMissing ? null : extractScheme(parsedUrl.protocol);
74-
const rawHost = extractHost(parsedUrl.hostname); // has square brackets for IPv6
75-
const host = unescapeIPv6Address(rawHost); // no square brackets for IPv6
73+
const scheme = sanitized.schemeMissing ? null : extractScheme(parsedUrl.scheme);
74+
const host = extractHost(parsedUrl.host); // no square brackets for IPv6
75+
const formattedHost = formatHost(host); // has square brackets for IPv6
7676
const port = extractPort(parsedUrl.port, scheme);
77-
const hostAndPort = `${rawHost}:${port}`;
78-
const query = parsedUrl.query;
77+
const hostAndPort = `${formattedHost}:${port}`;
78+
const query = extractQuery(parsedUrl.query, url);
7979

8080
return new Url(scheme, host, port, hostAndPort, query);
8181
}
@@ -84,8 +84,8 @@ function sanitizeUrl(url) {
8484
url = url.trim();
8585

8686
if (url.indexOf('://') === -1) {
87-
// url does not contain scheme, add dummy 'http://' to make parser work correctly
88-
return {schemeMissing: true, url: `http://${url}`};
87+
// url does not contain scheme, add dummy 'none://' to make parser work correctly
88+
return {schemeMissing: true, url: `none://${url}`};
8989
}
9090

9191
return {schemeMissing: false, url: url};
@@ -168,17 +168,12 @@ function escapeIPv6Address(address) {
168168
}
169169
}
170170

171-
function unescapeIPv6Address(address) {
172-
const startsWithSquareBracket = address.charAt(0) === '[';
173-
const endsWithSquareBracket = address.charAt(address.length - 1) === ']';
174-
175-
if (!startsWithSquareBracket && !endsWithSquareBracket) {
176-
return address;
177-
} else if (startsWithSquareBracket && endsWithSquareBracket) {
178-
return address.substring(1, address.length - 1);
179-
} else {
180-
throw new Error(`Illegal IPv6 address ${address}`);
171+
function formatHost(host) {
172+
if (!host) {
173+
throw new Error(`Illegal host ${host}`);
181174
}
175+
const isIPv6Address = host.indexOf(':') >= 0;
176+
return isIPv6Address ? escapeIPv6Address(host) : host;
182177
}
183178

184179
function formatIPv4Address(address, port) {

src/v1/routing-driver.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,19 @@ import LeastConnectedLoadBalancingStrategy, {LEAST_CONNECTED_STRATEGY_NAME} from
2424
import RoundRobinLoadBalancingStrategy, {ROUND_ROBIN_STRATEGY_NAME} from './internal/round-robin-load-balancing-strategy';
2525

2626
/**
27-
* A driver that supports routing in a core-edge cluster.
27+
* A driver that supports routing in a causal cluster.
2828
* @private
2929
*/
3030
class RoutingDriver extends Driver {
3131

32-
constructor(url, routingContext, userAgent, token = {}, config = {}) {
33-
super(url, userAgent, token, validateConfig(config));
32+
constructor(hostPort, routingContext, userAgent, token = {}, config = {}) {
33+
super(hostPort, userAgent, token, validateConfig(config));
3434
this._routingContext = routingContext;
3535
}
3636

37-
_createConnectionProvider(address, connectionPool, driverOnErrorCallback) {
37+
_createConnectionProvider(hostPort, connectionPool, driverOnErrorCallback) {
3838
const loadBalancingStrategy = RoutingDriver._createLoadBalancingStrategy(this._config, connectionPool);
39-
return new LoadBalancer(address, this._routingContext, connectionPool, loadBalancingStrategy, driverOnErrorCallback);
39+
return new LoadBalancer(hostPort, this._routingContext, connectionPool, loadBalancingStrategy, driverOnErrorCallback);
4040
}
4141

4242
_createSession(mode, connectionProvider, bookmark, config) {
@@ -46,14 +46,14 @@ class RoutingDriver extends Driver {
4646
return error;
4747
}
4848

49-
const url = conn.url;
49+
const hostPort = conn.hostPort;
5050

5151
if (error.code === SESSION_EXPIRED || isDatabaseUnavailable(error)) {
52-
this._connectionProvider.forget(url);
52+
this._connectionProvider.forget(hostPort);
5353
return error;
5454
} else if (isFailureToWrite(error)) {
55-
this._connectionProvider.forgetWriter(url);
56-
return newError('No longer possible to write to server at ' + url, SESSION_EXPIRED);
55+
this._connectionProvider.forgetWriter(hostPort);
56+
return newError('No longer possible to write to server at ' + hostPort, SESSION_EXPIRED);
5757
} else {
5858
return error;
5959
}

test/internal/url-util.test.js

+25
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,31 @@ describe('url-util', () => {
722722
expect(parse('https://localhost').port).toEqual(urlUtil.defaultPortForScheme('https'));
723723
});
724724

725+
it('should parse URLs with port 80', () => {
726+
['http', 'https', 'ws', 'wss', 'bolt', 'bolt+routing'].forEach(scheme => {
727+
verifyUrl(`${scheme}://localhost:80`, {
728+
scheme: scheme,
729+
host: 'localhost',
730+
port: 80
731+
});
732+
});
733+
734+
['localhost', '127.0.0.1', '192.168.10.29'].forEach(host => {
735+
verifyUrl(`${host}:80`, {
736+
host: host,
737+
port: 80
738+
});
739+
});
740+
741+
['::1', '1afc:0:a33:85a3::ff2f'].forEach(host => {
742+
verifyUrl(`[${host}]:80`, {
743+
host: host,
744+
port: 80,
745+
ipv6: true
746+
});
747+
});
748+
});
749+
725750
function verifyUrl(urlString, expectedUrl) {
726751
const url = parse(urlString);
727752

test/v1/driver.test.js

+23
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import FakeConnection from '../internal/fake-connection';
2222
import lolex from 'lolex';
2323
import {DEFAULT_ACQUISITION_TIMEOUT, DEFAULT_MAX_SIZE} from '../../src/v1/internal/pool-config';
2424
import {ServerVersion, VERSION_3_1_0} from '../../src/v1/internal/server-version';
25+
import testUtils from '../internal/test-utils';
2526

2627
describe('driver', () => {
2728

@@ -77,6 +78,28 @@ describe('driver', () => {
7778
startNewTransaction(driver);
7879
}, 10000);
7980

81+
it('should fail with correct error message when connecting to port 80', done => {
82+
if (testUtils.isClient()) {
83+
// good error message is not available in browser
84+
done();
85+
return;
86+
}
87+
88+
driver = neo4j.driver('bolt://localhost:80', sharedNeo4j.authToken);
89+
90+
driver.session().run('RETURN 1').then(result => {
91+
done.fail('Should not be able to connect. Result: ' + JSON.stringify(result));
92+
}).catch(error => {
93+
const doesNotContainAddress = error.message.indexOf(':80') < 0;
94+
if (doesNotContainAddress) {
95+
done.fail(`Expected to contain ':80' but was: ${error.message}`);
96+
} else {
97+
expect(error.code).toEqual(neo4j.error.SERVICE_UNAVAILABLE);
98+
done();
99+
}
100+
});
101+
});
102+
80103
it('should handle wrong scheme', () => {
81104
expect(() => neo4j.driver("tank://localhost", sharedNeo4j.authToken))
82105
.toThrow(new Error('Unknown scheme: tank'));

0 commit comments

Comments
 (0)