Skip to content

Commit b4f74cc

Browse files
authored
Introduce bolt+unix scheme support (#1586)
The new `bolt+unix` scheme allows connecting to Neo4j server over a Unix socket. Example: ```java try (var driver = GraphDatabase.driver("bolt+unix:///var/run/neo4j.sock")) { // use the driver var result = driver.executableQuery("SHOW DATABASES") .withConfig(QueryConfig.builder().withDatabase("system").build()) .execute(); result.records().forEach(System.out::println); } ```
1 parent c85bbf5 commit b4f74cc

File tree

11 files changed

+168
-155
lines changed

11 files changed

+168
-155
lines changed

driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import io.netty.channel.local.LocalAddress;
2525
import io.netty.util.concurrent.EventExecutorGroup;
2626
import java.net.URI;
27+
import java.nio.file.Files;
28+
import java.nio.file.Path;
2729
import java.time.Clock;
2830
import java.util.LinkedHashSet;
2931
import java.util.Set;
@@ -111,7 +113,6 @@ public final Driver newInstance(
111113
ownsEventLoopGroup = false;
112114
}
113115

114-
var address = new InternalServerAddress(uri);
115116
var routingSettings = new RoutingSettings(config.routingTablePurgeDelayMillis(), new RoutingContext(uri));
116117

117118
EventExecutorGroup eventExecutorGroup = bootstrap.config().group();
@@ -122,7 +123,6 @@ public final Driver newInstance(
122123
return createDriver(
123124
uri,
124125
securityPlanManager,
125-
address,
126126
bootstrap.group(),
127127
routingSettings,
128128
retryLogic,
@@ -149,7 +149,6 @@ protected static MetricsProvider getOrCreateMetricsProvider(Config config, Clock
149149
private InternalDriver createDriver(
150150
URI uri,
151151
BoltSecurityPlanManager securityPlanManager,
152-
ServerAddress address,
153152
EventLoopGroup eventLoopGroup,
154153
RoutingSettings routingSettings,
155154
RetryLogic retryLogic,
@@ -159,11 +158,29 @@ private InternalDriver createDriver(
159158
boolean ownsEventLoopGroup,
160159
Supplier<Rediscovery> rediscoverySupplier) {
161160
BoltConnectionProvider boltConnectionProvider = null;
161+
BoltServerAddress address;
162+
if (Scheme.BOLT_UNIX_URI_SCHEME.equals(uri.getScheme())) {
163+
var path = Path.of(uri.getPath());
164+
if (!Files.exists(path)) {
165+
throw new IllegalArgumentException(String.format("%s does not exist", path));
166+
}
167+
address = new BoltServerAddress(path);
168+
} else {
169+
var port = uri.getPort();
170+
if (port == -1) {
171+
port = InternalServerAddress.DEFAULT_PORT;
172+
}
173+
if (port >= 0 && port <= 65_535) {
174+
address = new BoltServerAddress(uri.getHost(), port);
175+
} else {
176+
throw new IllegalArgumentException("Illegal port: " + port);
177+
}
178+
}
162179
try {
163180
boltConnectionProvider =
164181
createBoltConnectionProvider(uri, config, eventLoopGroup, routingSettings, rediscoverySupplier);
165182
boltConnectionProvider.init(
166-
new BoltServerAddress(address.host(), address.port()),
183+
address,
167184
new RoutingContext(uri),
168185
DriverInfoUtil.boltAgent(),
169186
config.userAgent(),

driver/src/main/java/org/neo4j/driver/internal/InternalServerAddress.java

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ private static void requireValidPort(int port) {
3535
throw new IllegalArgumentException("Illegal port: " + port);
3636
}
3737

38-
public InternalServerAddress(String address) {
39-
this(uriFrom(address));
40-
}
41-
4238
public InternalServerAddress(URI uri) {
4339
this(hostFrom(uri), portFrom(uri));
4440
}
@@ -64,43 +60,6 @@ private static RuntimeException invalidAddressFormat(String address) {
6460
return new IllegalArgumentException("Invalid address format `" + address + "`");
6561
}
6662

67-
@SuppressWarnings("DuplicatedCode")
68-
private static URI uriFrom(String address) {
69-
String scheme;
70-
String hostPort;
71-
72-
var schemeSplit = address.split("://");
73-
if (schemeSplit.length == 1) {
74-
// URI can't parse addresses without scheme, prepend fake "bolt://" to reuse the parsing facility
75-
scheme = "bolt://";
76-
hostPort = hostPortFrom(schemeSplit[0]);
77-
} else if (schemeSplit.length == 2) {
78-
scheme = schemeSplit[0] + "://";
79-
hostPort = hostPortFrom(schemeSplit[1]);
80-
} else {
81-
throw invalidAddressFormat(address);
82-
}
83-
84-
return URI.create(scheme + hostPort);
85-
}
86-
87-
private static String hostPortFrom(String address) {
88-
if (address.startsWith("[")) {
89-
// expected to be an IPv6 address like [::1] or [::1]:7687
90-
return address;
91-
}
92-
93-
var containsSingleColon = address.indexOf(":") == address.lastIndexOf(":");
94-
if (containsSingleColon) {
95-
// expected to be an IPv4 address with or without port like 127.0.0.1 or 127.0.0.1:7687
96-
return address;
97-
}
98-
99-
// address contains multiple colons and does not start with '['
100-
// expected to be an IPv6 address without brackets
101-
return "[" + address + "]";
102-
}
103-
10463
@Override
10564
public String toString() {
10665
return String.format("%s:%d", host, port);

driver/src/main/java/org/neo4j/driver/internal/Scheme.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class Scheme {
2222
public static final String BOLT_URI_SCHEME = "bolt";
2323
public static final String BOLT_HIGH_TRUST_URI_SCHEME = "bolt+s";
2424
public static final String BOLT_LOW_TRUST_URI_SCHEME = "bolt+ssc";
25+
public static final String BOLT_UNIX_URI_SCHEME = "bolt+unix";
2526
public static final String NEO4J_URI_SCHEME = "neo4j";
2627
public static final String NEO4J_HIGH_TRUST_URI_SCHEME = "neo4j+s";
2728
public static final String NEO4J_LOW_TRUST_URI_SCHEME = "neo4j+ssc";
@@ -34,6 +35,7 @@ public static void validateScheme(String scheme) {
3435
case BOLT_URI_SCHEME,
3536
BOLT_LOW_TRUST_URI_SCHEME,
3637
BOLT_HIGH_TRUST_URI_SCHEME,
38+
BOLT_UNIX_URI_SCHEME,
3739
NEO4J_URI_SCHEME,
3840
NEO4J_LOW_TRUST_URI_SCHEME,
3941
NEO4J_HIGH_TRUST_URI_SCHEME -> {}

driver/src/main/java/org/neo4j/driver/internal/bolt/api/BoltServerAddress.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static java.util.Objects.requireNonNull;
2020

2121
import java.net.URI;
22+
import java.nio.file.Path;
2223
import java.util.Objects;
2324
import java.util.stream.Stream;
2425

@@ -35,6 +36,7 @@ public class BoltServerAddress {
3536
// resolved IP address.
3637
protected final int port;
3738
private final String stringValue;
39+
private final Path path;
3840

3941
public BoltServerAddress(String address) {
4042
this(uriFrom(address));
@@ -55,6 +57,15 @@ public BoltServerAddress(String host, String connectionHost, int port) {
5557
this.stringValue = host.equals(connectionHost)
5658
? String.format("%s:%d", host, port)
5759
: String.format("%s(%s):%d", host, connectionHost, port);
60+
this.path = null;
61+
}
62+
63+
public BoltServerAddress(Path path) {
64+
this.host = path.toString();
65+
this.connectionHost = this.host;
66+
this.port = -1;
67+
this.stringValue = this.host;
68+
this.path = path;
5869
}
5970

6071
@Override
@@ -91,6 +102,10 @@ public String connectionHost() {
91102
return connectionHost;
92103
}
93104

105+
public Path path() {
106+
return path;
107+
}
108+
94109
/**
95110
* Create a stream of unicast addresses.
96111
* <p>
@@ -115,7 +130,6 @@ private static int portFrom(URI uri) {
115130
return port == -1 ? DEFAULT_PORT : port;
116131
}
117132

118-
@SuppressWarnings("DuplicatedCode")
119133
private static URI uriFrom(String address) {
120134
String scheme;
121135
String hostPort;

driver/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/ConnectionProvider.java

Lines changed: 0 additions & 48 deletions
This file was deleted.

driver/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/ConnectionProviders.java

Lines changed: 0 additions & 34 deletions
This file was deleted.

driver/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/NettyBoltConnectionProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public final class NettyBoltConnectionProvider implements BoltConnectionProvider
5353
private final LoggingProvider logging;
5454
private final System.Logger log;
5555

56-
private final ConnectionProvider connectionProvider;
56+
private final NettyConnectionProvider connectionProvider;
5757

5858
private BoltServerAddress address;
5959

@@ -76,7 +76,7 @@ public NettyBoltConnectionProvider(
7676
this.logging = Objects.requireNonNull(logging);
7777
this.log = logging.getLog(getClass());
7878
this.connectionProvider =
79-
ConnectionProviders.netty(eventLoopGroup, clock, domainNameResolver, localAddress, logging);
79+
new NettyConnectionProvider(eventLoopGroup, clock, domainNameResolver, localAddress, logging);
8080
}
8181

8282
@Override

driver/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/NettyConnectionProvider.java

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525
import io.netty.channel.EventLoopGroup;
2626
import io.netty.channel.local.LocalAddress;
2727
import io.netty.channel.local.LocalChannel;
28+
import io.netty.channel.socket.nio.NioDomainSocketChannel;
2829
import io.netty.channel.socket.nio.NioSocketChannel;
2930
import io.netty.resolver.AddressResolverGroup;
3031
import java.net.InetSocketAddress;
3132
import java.net.SocketAddress;
33+
import java.net.UnixDomainSocketAddress;
3234
import java.time.Clock;
3335
import java.util.Map;
3436
import java.util.concurrent.CompletableFuture;
@@ -52,7 +54,7 @@
5254
import org.neo4j.driver.internal.bolt.basicimpl.messaging.BoltProtocol;
5355
import org.neo4j.driver.internal.bolt.basicimpl.spi.Connection;
5456

55-
public final class NettyConnectionProvider implements ConnectionProvider {
57+
public final class NettyConnectionProvider {
5658
private final EventLoopGroup eventLoopGroup;
5759
private final Clock clock;
5860
private final DomainNameResolver domainNameResolver;
@@ -75,7 +77,6 @@ public NettyConnectionProvider(
7577
this.logging = logging;
7678
}
7779

78-
@Override
7980
public CompletionStage<Connection> acquireConnection(
8081
BoltServerAddress address,
8182
SecurityPlan securityPlan,
@@ -90,27 +91,9 @@ public CompletionStage<Connection> acquireConnection(
9091
CompletableFuture<Long> latestAuthMillisFuture,
9192
NotificationConfig notificationConfig,
9293
MetricsListener metricsListener) {
93-
var bootstrap = new Bootstrap();
94-
bootstrap
95-
.group(this.eventLoopGroup)
96-
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutMillis)
97-
.channel(localAddress != null ? LocalChannel.class : NioSocketChannel.class)
98-
.resolver(addressResolverGroup)
99-
.handler(new NettyChannelInitializer(address, securityPlan, connectTimeoutMillis, clock, logging));
100-
101-
SocketAddress socketAddress;
102-
if (localAddress == null) {
103-
try {
104-
socketAddress =
105-
new InetSocketAddress(domainNameResolver.resolve(address.connectionHost())[0], address.port());
106-
} catch (Throwable t) {
107-
socketAddress = InetSocketAddress.createUnresolved(address.connectionHost(), address.port());
108-
}
109-
} else {
110-
socketAddress = localAddress;
111-
}
11294

113-
return installChannelConnectedListeners(address, bootstrap.connect(socketAddress), connectTimeoutMillis)
95+
return installChannelConnectedListeners(
96+
address, connect(address, securityPlan, connectTimeoutMillis), connectTimeoutMillis)
11497
.thenCompose(channel -> BoltProtocol.forChannel(channel)
11598
.initializeChannel(
11699
channel,
@@ -124,6 +107,39 @@ public CompletionStage<Connection> acquireConnection(
124107
.thenApply(channel -> new NetworkConnection(channel, logging));
125108
}
126109

110+
private ChannelFuture connect(BoltServerAddress address, SecurityPlan securityPlan, int connectTimeoutMillis) {
111+
Class<? extends Channel> channelClass;
112+
SocketAddress socketAddress;
113+
114+
if (localAddress != null) {
115+
channelClass = LocalChannel.class;
116+
socketAddress = localAddress;
117+
} else {
118+
if (address.path() != null) {
119+
channelClass = NioDomainSocketChannel.class;
120+
socketAddress = UnixDomainSocketAddress.of(address.path());
121+
} else {
122+
channelClass = NioSocketChannel.class;
123+
try {
124+
socketAddress = new InetSocketAddress(
125+
domainNameResolver.resolve(address.connectionHost())[0], address.port());
126+
} catch (Throwable t) {
127+
socketAddress = InetSocketAddress.createUnresolved(address.connectionHost(), address.port());
128+
}
129+
}
130+
}
131+
132+
var bootstrap = new Bootstrap();
133+
bootstrap
134+
.group(this.eventLoopGroup)
135+
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutMillis)
136+
.channel(channelClass)
137+
.resolver(addressResolverGroup)
138+
.handler(new NettyChannelInitializer(address, securityPlan, connectTimeoutMillis, clock, logging));
139+
140+
return bootstrap.connect(socketAddress);
141+
}
142+
127143
private CompletionStage<Channel> installChannelConnectedListeners(
128144
BoltServerAddress address, ChannelFuture channelConnected, int connectTimeoutMillis) {
129145
var pipeline = channelConnected.channel().pipeline();

driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555

5656
class DriverFactoryTest {
5757
private static Stream<String> testUris() {
58-
return Stream.of("bolt://localhost:7687", "neo4j://localhost:7687");
58+
return Stream.of("bolt://localhost:7687", "bolt+unix://localhost:7687", "neo4j://localhost:7687");
5959
}
6060

6161
@ParameterizedTest

0 commit comments

Comments
 (0)