Skip to content

Commit

Permalink
Add config options for TCP KeepAlive and NoDelay
Browse files Browse the repository at this point in the history
NoDelay is enabled by default.

[resolves #177]
  • Loading branch information
mp911de committed Oct 22, 2020
1 parent 31108ab commit 24dd66a
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 9 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ Mono<Connection> connectionMono = Mono.from(connectionFactory.create());
| `sendStringParametersAsUnicode` | Configure whether to send character data as unicode (NVARCHAR, NCHAR, NTEXT) or whether to use the database encoding, defaults to `true`. If disabled, `CharSequence` data is sent using the database-specific collation such as ASCII/MBCS instead of Unicode.
| `sslTunnel` | Enables SSL tunnel usage when using a SSL tunnel or SSL terminator in front of SQL Server. Accepts `Function<SslContextBuilder, SslContextBuilder>` to customize the SSL tunnel settings. SSL tunneling is not related to SQL Server's built-in SSL support. _(Optional)_
| `sslContextBuilderCustomizer` | SSL Context customizer to configure SQL Server's built-in SSL support (`Function<SslContextBuilder, SslContextBuilder>`) _(Optional)_
| `tcpKeepAlive` | Enable/disable TCP KeepAlive. Disabled by default. _(Optional)_
| `tcpNoDelay` | Enable/disable TCP NoDelay. Enabled by default. _(Optional)_
| `trustStoreType` | Type of the TrustStore. Defaults to `KeyStore.getDefaultType()`. _(Optional)_
| `trustStore` | Path to the certificate TrustStore file. _(Optional)_
| `trustStorePassword` | Password used to check the integrity of the TrustStore data. _(Optional)_
Expand Down
79 changes: 70 additions & 9 deletions src/main/java/io/r2dbc/mssql/MssqlConnectionConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
Expand Down Expand Up @@ -98,6 +99,10 @@ public final class MssqlConnectionConfiguration {

private final String username;

private final boolean tcpKeepAlive;

private final boolean tcpNoDelay;

@Nullable
private final File trustStore;

Expand All @@ -110,7 +115,8 @@ public final class MssqlConnectionConfiguration {
private MssqlConnectionConfiguration(@Nullable String applicationName, @Nullable UUID connectionId, Duration connectTimeout, @Nullable String database, String host, String hostNameInCertificate,
CharSequence password, Predicate<String> preferCursoredExecution, int port, boolean sendStringParametersAsUnicode, boolean ssl,
Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer,
@Nullable Function<SslContextBuilder, SslContextBuilder> sslTunnelSslContextBuilderCustomizer, @Nullable File trustStore, @Nullable String trustStoreType,
@Nullable Function<SslContextBuilder, SslContextBuilder> sslTunnelSslContextBuilderCustomizer, boolean tcpKeepAlive, boolean tcpNoDelay,
@Nullable File trustStore, @Nullable String trustStoreType,
@Nullable char[] trustStorePassword, String username) {

this.applicationName = applicationName;
Expand All @@ -126,6 +132,8 @@ private MssqlConnectionConfiguration(@Nullable String applicationName, @Nullable
this.ssl = ssl;
this.sslContextBuilderCustomizer = sslContextBuilderCustomizer;
this.sslTunnelSslContextBuilderCustomizer = sslTunnelSslContextBuilderCustomizer;
this.tcpKeepAlive = tcpKeepAlive;
this.tcpNoDelay = tcpNoDelay;
this.trustStore = trustStore;
this.trustStoreType = trustStoreType;
this.trustStorePassword = trustStorePassword;
Expand Down Expand Up @@ -166,16 +174,12 @@ MssqlConnectionConfiguration withRedirect(Redirect redirect) {

return new MssqlConnectionConfiguration(this.applicationName, this.connectionId, this.connectTimeout, this.database, redirectServerName, hostNameInCertificate, this.password,
this.preferCursoredExecution, redirect.getPort(), this.sendStringParametersAsUnicode, this.ssl, this.sslContextBuilderCustomizer, this.sslTunnelSslContextBuilderCustomizer,
this.trustStore,
this.trustStoreType,
this.trustStorePassword, this.username);
this.tcpKeepAlive, this.tcpNoDelay, this.trustStore, this.trustStoreType, this.trustStorePassword, this.username);
}

ClientConfiguration toClientConfiguration() {
return new DefaultClientConfiguration(this.connectTimeout, this.host, this.hostNameInCertificate, this.port, this.ssl, this.sslContextBuilderCustomizer,
this.sslTunnelSslContextBuilderCustomizer,
this.trustStore, this.trustStoreType,
this.trustStorePassword);
this.sslTunnelSslContextBuilderCustomizer, this.tcpKeepAlive, this.tcpNoDelay, this.trustStore, this.trustStoreType, this.trustStorePassword);
}

ConnectionOptions toConnectionOptions() {
Expand All @@ -199,6 +203,8 @@ public String toString() {
sb.append(", ssl=").append(this.ssl);
sb.append(", sslContextBuilderCustomizer=").append(this.sslContextBuilderCustomizer);
sb.append(", sslTunnelSslContextBuilderCustomizer=").append(this.sslTunnelSslContextBuilderCustomizer);
sb.append(", tcpKeepAlive=\"").append(this.tcpKeepAlive).append("\"");
sb.append(", tcpNoDelay=\"").append(this.tcpNoDelay).append("\"");
sb.append(", trustStore=\"").append(this.trustStore).append("\"");
sb.append(", trustStorePassword=\"").append(repeat(this.trustStorePassword == null ? 0 : this.trustStorePassword.length, "*")).append('\"');
sb.append(", trustStoreType=\"").append(this.trustStoreType).append("\"");
Expand Down Expand Up @@ -253,6 +259,14 @@ boolean useSsl() {
return this.ssl;
}

boolean isTcpKeepAlive() {
return this.tcpKeepAlive;
}

boolean isTcpNoDelay() {
return this.tcpNoDelay;
}

String getUsername() {
return this.username;
}
Expand Down Expand Up @@ -337,6 +351,10 @@ public static final class Builder {

private String username;

private boolean tcpKeepAlive = false;

private boolean tcpNoDelay = true;

@Nullable
private File trustStore;

Expand Down Expand Up @@ -547,6 +565,32 @@ public Builder username(String username) {
return this;
}

/**
* Configure TCP KeepAlive. Disabled by default.
*
* @param enabled whether to enable/disable TCP KeepAlive
* @return this {@link Builder}
* @see Socket#setKeepAlive(boolean)
* @since 0.8.5
*/
public Builder tcpKeepAlive(boolean enabled) {
this.tcpKeepAlive = enabled;
return this;
}

/**
* Configure TCP NoDelay. Enabled by default.
*
* @param enabled whether to enable/disable TCP NoDelay
* @return this {@link Builder}
* @see Socket#setTcpNoDelay(boolean)
* @since 0.8.5
*/
public Builder tcpNoDelay(boolean enabled) {
this.tcpNoDelay = enabled;
return this;
}

/**
* Configure the trust store type.
*
Expand Down Expand Up @@ -609,7 +653,8 @@ public MssqlConnectionConfiguration build() {
}

return new MssqlConnectionConfiguration(this.applicationName, this.connectionId, this.connectTimeout, this.database, this.host, this.hostNameInCertificate, this.password,
this.preferCursoredExecution, this.port, this.sendStringParametersAsUnicode, this.ssl, this.sslContextBuilderCustomizer, this.sslTunnelSslContextBuilderCustomizer, this.trustStore,
this.preferCursoredExecution, this.port, this.sendStringParametersAsUnicode, this.ssl, this.sslContextBuilderCustomizer, this.sslTunnelSslContextBuilderCustomizer, tcpKeepAlive,
tcpNoDelay, this.trustStore,
this.trustStoreType,
this.trustStorePassword, this.username);
}
Expand All @@ -633,6 +678,10 @@ static class DefaultClientConfiguration implements ClientConfiguration {
@Nullable
private final Function<SslContextBuilder, SslContextBuilder> sslTunnelSslContextBuilderCustomizer;

private final boolean tcpKeepAlive;

private final boolean tcpNoDelay;

@Nullable
private final File trustStore;

Expand All @@ -645,7 +694,7 @@ static class DefaultClientConfiguration implements ClientConfiguration {
DefaultClientConfiguration(Duration connectTimeout, String host, String hostNameInCertificate, int port, boolean ssl,
Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer,
@Nullable Function<SslContextBuilder, SslContextBuilder> sslTunnelSslContextBuilderCustomizer
, @Nullable File trustStore,
, boolean tcpKeepAlive, boolean tcpNoDelay, @Nullable File trustStore,
@Nullable String trustStoreType, @Nullable char[] trustStorePassword) {

this.connectTimeout = connectTimeout;
Expand All @@ -655,6 +704,8 @@ static class DefaultClientConfiguration implements ClientConfiguration {
this.ssl = ssl;
this.sslContextBuilderCustomizer = sslContextBuilderCustomizer;
this.sslTunnelSslContextBuilderCustomizer = sslTunnelSslContextBuilderCustomizer;
this.tcpKeepAlive = tcpKeepAlive;
this.tcpNoDelay = tcpNoDelay;
this.trustStore = trustStore;
this.trustStoreType = trustStoreType;
this.trustStorePassword = trustStorePassword;
Expand All @@ -675,6 +726,16 @@ public Duration getConnectTimeout() {
return this.connectTimeout;
}

@Override
public boolean isTcpKeepAlive() {
return this.tcpKeepAlive;
}

@Override
public boolean isTcpNoDelay() {
return this.tcpNoDelay;
}

@Override
public ConnectionProvider getConnectionProvider() {
return ConnectionProvider.newConnection();
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/io/r2dbc/mssql/MssqlConnectionFactoryProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ public final class MssqlConnectionFactoryProvider implements ConnectionFactoryPr
*/
public static final Option<Function<SslContextBuilder, SslContextBuilder>> SSL_TUNNEL = Option.valueOf("sslTunnel");

/**
* Enable/Disable TCP KeepAlive.
*
* @since 0.8.5
*/
public static final Option<Boolean> TCP_KEEPALIVE = Option.valueOf("tcpKeepAlive");

/**
* Enable/Disable TCP NoDelay.
*
* @since 0.8.5
*/
public static final Option<Boolean> TCP_NODELAY = Option.valueOf("tcpNoDelay");

/**
* Type of the TrustStore.
*
Expand Down Expand Up @@ -159,6 +173,8 @@ public MssqlConnectionFactory create(ConnectionFactoryOptions connectionFactoryO
}
});

mapper.from(TCP_KEEPALIVE).map(OptionMapper::toBoolean).to(builder::tcpKeepAlive);
mapper.from(TCP_NODELAY).map(OptionMapper::toBoolean).to(builder::tcpNoDelay);
mapper.from(TRUST_STORE).map(OptionMapper::toFile).to(builder::trustStore);
mapper.from(TRUST_STORE_TYPE).to(builder::trustStoreType);
mapper.from(TRUST_STORE_PASSWORD).map(it -> it instanceof String ? ((String) it).toCharArray() : (char[]) it).to(builder::trustStorePassword);
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/io/r2dbc/mssql/client/ClientConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ public interface ClientConfiguration extends SslConfiguration {
*/
Duration getConnectTimeout();

/**
* @return whether TCP KeepAlive is enabled.
* @since 0.8.5
*/
boolean isTcpKeepAlive();

/**
* @return whether TCP NoDelay is enabled.
* @since 0.8.5
*/
boolean isTcpNoDelay();

/**
* @return connection provider.
*/
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/io/r2dbc/mssql/client/ReactorNettyClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,16 @@ public Duration getConnectTimeout() {
return connectTimeout;
}

@Override
public boolean isTcpKeepAlive() {
return false;
}

@Override
public boolean isTcpNoDelay() {
return true;
}

@Override
public ConnectionProvider getConnectionProvider() {
return ConnectionProvider.newConnection();
Expand Down Expand Up @@ -419,6 +429,8 @@ public static Mono<ReactorNettyClient> connect(ClientConfiguration configuration

Mono<? extends Connection> connection = TcpClient.create(configuration.getConnectionProvider())
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(configuration.getConnectTimeout().toMillis()))
.option(ChannelOption.SO_KEEPALIVE, configuration.isTcpKeepAlive())
.option(ChannelOption.TCP_NODELAY, configuration.isTcpNoDelay())
.host(configuration.getHost())
.port(configuration.getPort())
.connect()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.MSSQL_DRIVER;
import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.SSL_CONTEXT_BUILDER_CUSTOMIZER;
import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.SSL_TUNNEL;
import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.TCP_KEEPALIVE;
import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.TCP_NODELAY;
import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.TRUST_STORE;
import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.TRUST_STORE_PASSWORD;
import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.TRUST_STORE_TYPE;
Expand All @@ -38,6 +40,7 @@
import static io.r2dbc.spi.ConnectionFactoryOptions.PORT;
import static io.r2dbc.spi.ConnectionFactoryOptions.SSL;
import static io.r2dbc.spi.ConnectionFactoryOptions.USER;
import static io.r2dbc.spi.ConnectionFactoryOptions.builder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;

Expand Down Expand Up @@ -239,6 +242,34 @@ void shouldConfigureWithSslTunnelCustomizer() {
assertThat(factory.getClientConfiguration().getSslTunnelConfiguration().isSslEnabled()).isTrue();
}

@Test
void shouldConfigureTcpKeepAlive() {

MssqlConnectionFactory factory = this.provider.create(builder()
.option(DRIVER, MSSQL_DRIVER)
.option(HOST, "test-host")
.option(PASSWORD, "test-password")
.option(USER, "test-user")
.option(TCP_KEEPALIVE, true)
.build());

assertThat(factory.getClientConfiguration().isTcpKeepAlive()).isTrue();
}

@Test
void shouldConfigureTcpNoDelay() {

MssqlConnectionFactory factory = this.provider.create(builder()
.option(DRIVER, MSSQL_DRIVER)
.option(HOST, "test-host")
.option(PASSWORD, "test-password")
.option(USER, "test-user")
.option(TCP_NODELAY, true)
.build());

assertThat(factory.getClientConfiguration().isTcpNoDelay()).isTrue();
}

@Test
void shouldConfigureWithTrustStoreCustomizer() {

Expand Down Expand Up @@ -303,5 +334,7 @@ static class MyPredicate implements Predicate<String> {
public boolean test(String s) {
return s.equals("foo");
}

}

}

0 comments on commit 24dd66a

Please sign in to comment.