Skip to content

Commit 4cbc5e6

Browse files
committed
Support unix domain socket connections
We now support connections using unix domain sockets for local IPC transport instead of TCP usage. [resolves #181]
1 parent c21e5a6 commit 4cbc5e6

9 files changed

+331
-71
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This driver provides the following features:
66

77
* Login with username/password (MD5, SASL/SCRAM) or implicit trust
88
* SCRAM authentication
9+
* Unix Domain Socket transport
910
* TLS
1011
* Explicit transactions
1112
* Notifications
@@ -70,6 +71,7 @@ Mono<Connection> connectionMono = Mono.from(connectionFactory.create());
7071
| `driver` | Must be `postgresql`.
7172
| `host` | Server hostname to connect to
7273
| `port` | Server port to connect to. Defaults to `5432`. _(Optional)_
74+
| `socket` | Unix Domain Socket path to connect to as alternative to TCP. _(Optional)_
7375
| `username` | Login username
7476
| `password` | Login password _(Optional when using TLS Certificate authentication)_
7577
| `database` | Database to select. _(Optional)_

pom.xml

+14
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
<jsr305.version>3.0.2</jsr305.version>
3838
<junit.version>5.5.2</junit.version>
3939
<jmh.version>1.21</jmh.version>
40+
<netty.version>4.1.42.Final</netty.version>
4041
<mbr.version>0.1.0.RELEASE</mbr.version>
4142
<logback.version>1.2.3</logback.version>
4243
<mockito.version>3.0.0</mockito.version>
@@ -60,6 +61,13 @@
6061
<type>pom</type>
6162
<scope>import</scope>
6263
</dependency>
64+
<dependency>
65+
<groupId>io.netty</groupId>
66+
<artifactId>netty-bom</artifactId>
67+
<version>${netty.version}</version>
68+
<type>pom</type>
69+
<scope>import</scope>
70+
</dependency>
6371
<dependency>
6472
<groupId>org.junit</groupId>
6573
<artifactId>junit-bom</artifactId>
@@ -171,6 +179,12 @@
171179
<version>${hikari-cp.version}</version>
172180
<scope>test</scope>
173181
</dependency>
182+
<dependency>
183+
<groupId>io.netty</groupId>
184+
<artifactId>netty-transport-native-kqueue</artifactId>
185+
<classifier>osx-x86_64</classifier>
186+
<optional>true</optional>
187+
</dependency>
174188
<dependency>
175189
<groupId>org.testcontainers</groupId>
176190
<artifactId>postgresql</artifactId>

src/main/java/io/r2dbc/postgresql/PostgresqlConnectionConfiguration.java

+63-7
Original file line numberDiff line numberDiff line change
@@ -71,25 +71,28 @@ public final class PostgresqlConnectionConfiguration {
7171

7272
private final String schema;
7373

74+
private final String socket;
75+
7476
private final String username;
7577

7678
private final SSLConfig sslConfig;
7779

7880
private PostgresqlConnectionConfiguration(String applicationName, boolean autodetectExtensions,
79-
@Nullable Duration connectTimeout, @Nullable String database, List<Extension> extensions, boolean forceBinary, String host,
80-
@Nullable Map<String, String> options, @Nullable CharSequence password, int port, @Nullable String schema, String username,
81+
@Nullable Duration connectTimeout, @Nullable String database, List<Extension> extensions, boolean forceBinary, @Nullable String host,
82+
@Nullable Map<String, String> options, @Nullable CharSequence password, int port, @Nullable String schema, @Nullable String socket, String username,
8183
SSLConfig sslConfig) {
8284
this.applicationName = Assert.requireNonNull(applicationName, "applicationName must not be null");
8385
this.autodetectExtensions = autodetectExtensions;
8486
this.connectTimeout = connectTimeout;
8587
this.extensions = Assert.requireNonNull(extensions, "extensions must not be null");
8688
this.database = database;
8789
this.forceBinary = forceBinary;
88-
this.host = Assert.requireNonNull(host, "host must not be null");
90+
this.host = host;
8991
this.options = options;
9092
this.password = password;
9193
this.port = port;
9294
this.schema = schema;
95+
this.socket = socket;
9396
this.username = Assert.requireNonNull(username, "username must not be null");
9497
this.sslConfig = sslConfig;
9598
}
@@ -150,10 +153,22 @@ List<Extension> getExtensions() {
150153
return this.extensions;
151154
}
152155

156+
@Nullable
153157
String getHost() {
154158
return this.host;
155159
}
156160

161+
String getRequiredHost() {
162+
163+
String host = getHost();
164+
165+
if (host == null || host.isEmpty()) {
166+
throw new IllegalStateException("Connection is configured for socket connections and not for host usage");
167+
}
168+
169+
return host;
170+
}
171+
157172
@Nullable
158173
Map<String, String> getOptions() {
159174
return this.options;
@@ -173,6 +188,22 @@ String getSchema() {
173188
return this.schema;
174189
}
175190

191+
@Nullable
192+
String getSocket() {
193+
return this.socket;
194+
}
195+
196+
String getRequiredSocket() {
197+
198+
String socket = getSocket();
199+
200+
if (socket == null || socket.isEmpty()) {
201+
throw new IllegalStateException("Connection is configured to use host and port connections and not for socket usage");
202+
}
203+
204+
return socket;
205+
}
206+
176207
String getUsername() {
177208
return this.username;
178209
}
@@ -185,6 +216,10 @@ boolean isForceBinary() {
185216
return this.forceBinary;
186217
}
187218

219+
boolean isUseSocket() {
220+
return getSocket() != null;
221+
}
222+
188223
SSLConfig getSslConfig() {
189224
return this.sslConfig;
190225
}
@@ -223,6 +258,9 @@ public static final class Builder {
223258
@Nullable
224259
private String schema;
225260

261+
@Nullable
262+
private String socket;
263+
226264
@Nullable
227265
private String sslCert = null;
228266

@@ -275,16 +313,20 @@ public Builder autodetectExtensions(boolean autodetectExtensions) {
275313
*/
276314
public PostgresqlConnectionConfiguration build() {
277315

278-
if (this.host == null) {
279-
throw new IllegalArgumentException("host must not be null");
316+
if (this.host == null && this.socket == null) {
317+
throw new IllegalArgumentException("host or socket must not be null");
318+
}
319+
320+
if (this.host != null && this.socket != null) {
321+
throw new IllegalArgumentException("Connection must be configured for either host/port or socket usage but not both");
280322
}
281323

282324
if (this.username == null) {
283325
throw new IllegalArgumentException("username must not be null");
284326
}
285327

286328
return new PostgresqlConnectionConfiguration(this.applicationName, this.autodetectExtensions, this.connectTimeout, this.database, this.extensions, this.forceBinary, this.host,
287-
this.options, this.password, this.port, this.schema, this.username, this.createSslConfig());
329+
this.options, this.password, this.port, this.schema, this.socket, this.username, this.createSslConfig());
288330
}
289331

290332
/**
@@ -418,6 +460,19 @@ public Builder schema(@Nullable String schema) {
418460
return this;
419461
}
420462

463+
/**
464+
* Configure the unix domain socket to connect to.
465+
*
466+
* @param socket the socket path
467+
* @return this {@link Builder}
468+
* @throws IllegalArgumentException if {@code socket} is {@code null}
469+
*/
470+
public Builder socket(String socket) {
471+
this.socket = Assert.requireNonNull(socket, "host must not be null");
472+
sslMode(SSLMode.DISABLE);
473+
return this;
474+
}
475+
421476
/**
422477
* Configure ssl cert for client certificate authentication.
423478
*
@@ -488,6 +543,7 @@ public String toString() {
488543
", port=" + this.port +
489544
", schema='" + this.schema + '\'' +
490545
", username='" + this.username + '\'' +
546+
", socket='" + this.socket + '\'' +
491547
", sslMode='" + this.sslMode + '\'' +
492548
", sslRootCert='" + this.sslRootCert + '\'' +
493549
", sslCert='" + this.sslCert + '\'' +
@@ -520,7 +576,7 @@ public Builder username(String username) {
520576
}
521577

522578
private SSLConfig createSslConfig() {
523-
if (this.sslMode == SSLMode.DISABLE) {
579+
if (this.socket != null || this.sslMode == SSLMode.DISABLE) {
524580
return new SSLConfig(SSLMode.DISABLE, null, (hostname, session) -> true);
525581
}
526582
HostnameVerifier hostnameVerifier = this.sslHostnameVerifier;

src/main/java/io/r2dbc/postgresql/PostgresqlConnectionFactory.java

+26-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package io.r2dbc.postgresql;
1818

1919
import io.netty.buffer.ByteBufAllocator;
20+
import io.netty.channel.unix.DomainSocketAddress;
2021
import io.r2dbc.postgresql.authentication.AuthenticationHandler;
2122
import io.r2dbc.postgresql.authentication.PasswordAuthenticationHandler;
2223
import io.r2dbc.postgresql.authentication.SASLAuthenticationHandler;
@@ -37,8 +38,11 @@
3738
import org.reactivestreams.Publisher;
3839
import reactor.core.publisher.Flux;
3940
import reactor.core.publisher.Mono;
41+
import reactor.netty.resources.ConnectionProvider;
4042
import reactor.util.annotation.Nullable;
4143

44+
import java.net.InetSocketAddress;
45+
import java.net.SocketAddress;
4246
import java.util.ArrayList;
4347
import java.util.List;
4448
import java.util.Locale;
@@ -50,10 +54,12 @@
5054
*/
5155
public final class PostgresqlConnectionFactory implements ConnectionFactory {
5256

53-
private final Function<SSLConfig, Mono<? extends Client>> clientFactory;
54-
5557
private final PostgresqlConnectionConfiguration configuration;
5658

59+
private final SocketAddress endpoint;
60+
61+
private final Function<SSLConfig, Mono<? extends Client>> clientFactory;
62+
5763
private final Extensions extensions;
5864

5965
/**
@@ -63,17 +69,32 @@ public final class PostgresqlConnectionFactory implements ConnectionFactory {
6369
* @throws IllegalArgumentException if {@code configuration} is {@code null}
6470
*/
6571
public PostgresqlConnectionFactory(PostgresqlConnectionConfiguration configuration) {
66-
this.clientFactory = sslConfig -> ReactorNettyClient.connect(configuration.getHost(), configuration.getPort(), configuration.getConnectTimeout(), sslConfig).cast(Client.class);
6772
this.configuration = Assert.requireNonNull(configuration, "configuration must not be null");
73+
this.endpoint = createSocketAddress(configuration);
74+
this.clientFactory = sslConfig -> ReactorNettyClient.connect(ConnectionProvider.newConnection(), this.endpoint, configuration.getConnectTimeout(), sslConfig).cast(Client.class);
6875
this.extensions = getExtensions(configuration);
6976
}
7077

7178
PostgresqlConnectionFactory(Function<SSLConfig, Mono<? extends Client>> clientFactory, PostgresqlConnectionConfiguration configuration) {
72-
this.clientFactory = Assert.requireNonNull(clientFactory, "clientFactory must not be null");
7379
this.configuration = Assert.requireNonNull(configuration, "configuration must not be null");
80+
this.endpoint = createSocketAddress(configuration);
81+
this.clientFactory = Assert.requireNonNull(clientFactory, "clientFactory must not be null");
7482
this.extensions = getExtensions(configuration);
7583
}
7684

85+
private static SocketAddress createSocketAddress(PostgresqlConnectionConfiguration configuration) {
86+
87+
if (!configuration.isUseSocket()) {
88+
return InetSocketAddress.createUnresolved(configuration.getRequiredHost(), configuration.getPort());
89+
}
90+
91+
if (configuration.isUseSocket()) {
92+
return new DomainSocketAddress(configuration.getRequiredSocket());
93+
}
94+
95+
throw new IllegalArgumentException("Cannot create SocketAddress for " + configuration);
96+
}
97+
7798
private static Extensions getExtensions(PostgresqlConnectionConfiguration configuration) {
7899
Extensions extensions = Extensions.from(configuration.getExtensions());
79100

@@ -149,7 +170,7 @@ private Throwable cannotConnect(Throwable throwable) {
149170
}
150171

151172
return new PostgresConnectionException(
152-
String.format("Cannot connect to %s:%d", this.configuration.getHost(), this.configuration.getPort()), throwable
173+
String.format("Cannot connect to %s", this.endpoint), throwable
153174
);
154175
}
155176

0 commit comments

Comments
 (0)