Skip to content

Commit

Permalink
Merge pull request #43454 from mcruzdev/issue-43167
Browse files Browse the repository at this point in the history
Add client parameter when creating Vertx Redis client
  • Loading branch information
cescoffier authored Oct 3, 2024
2 parents e2c1d31 + d6264ad commit 9af0f74
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package io.quarkus.redis.deployment.client;

import jakarta.inject.Inject;

import org.assertj.core.api.Assertions;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.redis.client.RedisClientName;
import io.quarkus.redis.datasource.RedisDataSource;
import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.QuarkusTestResource;
import io.vertx.mutiny.redis.client.Response;
import io.vertx.redis.client.Command;

@QuarkusTestResource(RedisTestResource.class)
public class RedisConfigureClientNameTest {

@RegisterExtension
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class))
.overrideConfigKey("quarkus.redis.my-redis.hosts", "${quarkus.redis.tr}/1")
.overrideConfigKey("quarkus.redis.my-redis.configure-client-name", "true")
.overrideConfigKey("quarkus.redis.my-redis.client-name", "perfect-client-name")
.overrideConfigKey("quarkus.redis.no-configure.hosts", "${quarkus.redis.tr}/2")
.overrideConfigKey("quarkus.redis.no-configure.client-name", "i-am-not-applicable")
.overrideConfigKey("quarkus.redis.no-configure.configure-client-name", "false")
.overrideConfigKey("quarkus.redis.from-annotation.configure-client-name", "true")
.overrideConfigKey("quarkus.redis.from-annotation.hosts", "${quarkus.redis.tr}/3");

@Inject
@RedisClientName("my-redis")
RedisDataSource myRedis;

@Inject
@RedisClientName("no-configure")
RedisDataSource noConfigure;

@Inject
@RedisClientName("from-annotation")
RedisDataSource fromAnnotation;

@Test
void shouldConfigureClientNameCorrectly() {
Response executed = myRedis.execute(Command.CLIENT, "GETNAME");
Assertions.assertThat(executed).isNotNull();
Assertions.assertThat(executed.toString()).isEqualTo("perfect-client-name");
}

@Test
void shouldConfigureFromRedisClientNameAnnotation() {
Response executed = fromAnnotation.execute(Command.CLIENT, "GETNAME");
Assertions.assertThat(executed).isNotNull();
Assertions.assertThat(executed.toString()).isEqualTo("from-annotation");
}

@Test
void shouldNotConfigureClientName() {
Response executed = noConfigure.execute(Command.CLIENT, "GETNAME");
Assertions.assertThat(executed).isNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
public class RedisClientRecorder {

// Split client and DS recorders

private final RedisConfig config;
private static final Map<String, RedisClientAndApi> clients = new HashMap<>();
private static final Map<String, ReactiveRedisDataSourceImpl> dataSources = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configurePfxTrustOptions;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;

import org.jboss.logging.Logger;

Expand Down Expand Up @@ -41,6 +43,7 @@
public class VertxRedisClientFactory {

public static final String DEFAULT_CLIENT = "<default>";
public static String NON_RESERVED_URI_PATTERN = "[^a-zA-Z0-9\\-_.~]";

private static final Logger LOGGER = Logger.getLogger(VertxRedisClientFactory.class);

Expand All @@ -51,19 +54,30 @@ private VertxRedisClientFactory() {
public static Redis create(String name, Vertx vertx, RedisClientConfig config, TlsConfigurationRegistry tlsRegistry) {
RedisOptions options = new RedisOptions();

Consumer<Set<URI>> configureOptions = new Consumer<Set<URI>>() {
@Override
public void accept(Set<URI> uris) {
for (URI uri : uris) {
if (config.configureClientName()) {
String client = config.clientName().orElse(name);
String newURI = applyClientQueryParam(client, uri);
options.addConnectionString(newURI);
} else {
options.addConnectionString(uri.toString().trim());
}
}
}
};

List<URI> hosts = new ArrayList<>();
if (config.hosts().isPresent()) {
hosts.addAll(config.hosts().get());
for (URI uri : config.hosts().get()) {
options.addConnectionString(uri.toString().trim());
}
configureOptions.accept(config.hosts().get());
} else if (config.hostsProviderName().isPresent()) {
RedisHostsProvider hostsProvider = findProvider(config.hostsProviderName().get());
Set<URI> computedHosts = hostsProvider.getHosts();
hosts.addAll(computedHosts);
for (URI uri : computedHosts) {
options.addConnectionString(uri.toString());
}
configureOptions.accept(computedHosts);
} else {
throw new ConfigurationException("Redis host not configured - you must either configure 'quarkus.redis.hosts` or" +
" 'quarkus.redis.host-provider-name' and have a bean providing the hosts programmatically.");
Expand Down Expand Up @@ -109,6 +123,48 @@ public static Redis create(String name, Vertx vertx, RedisClientConfig config, T
return Redis.createClient(vertx, options);
}

public static String applyClientQueryParam(String client, URI uri) {

if (client.matches(".*" + NON_RESERVED_URI_PATTERN + ".*")) {
LOGGER.warn("The client query parameter contains reserved URI characters. " +
"This may result in an incorrect client name after URI encoding.");
}

String query = uri.getQuery();

boolean hasClient = hasRedisClientParameter(query);

if (hasClient) {
LOGGER.warnf("Your host already has a client name. The client name %s will be disregarded.", client);
return uri.toString().trim();
}

query = query == null ? "client=" + client
: uri.getQuery() + "&client=" + client;

try {
return new URI(
uri.getScheme(), uri.getAuthority(), uri.getPath(), query, uri.getFragment()).toString().trim();
} catch (URISyntaxException e) {
LOGGER.warnf("Was not possible to generate a new Redis URL with client query parameter, " +
"the value is: %s", client);
return uri.toString().trim();
}
}

private static boolean hasRedisClientParameter(String query) {
if (query != null) {
String[] pairs = query.split("&");
for (String pair : pairs) {
String[] keyValue = pair.split("=");
if (keyValue.length == 2 && keyValue[0].equals("client")) {
return true;
}
}
}
return false;
}

private static void customize(String name, RedisOptions options) {
if (Arc.container() != null) {
List<InstanceHandle<RedisOptionsCustomizer>> customizers = Arc.container().listAll(RedisOptionsCustomizer.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.Optional;
import java.util.Set;

import io.quarkus.redis.client.RedisClientName;
import io.quarkus.runtime.annotations.ConfigDocDefault;
import io.quarkus.runtime.annotations.ConfigDocSection;
import io.quarkus.runtime.annotations.ConfigGroup;
Expand Down Expand Up @@ -197,6 +198,29 @@ public interface RedisClientConfig {
@ConfigDocSection
TlsConfig tls();

/**
* The client name used to identify the connection.
* <p>
* If the {@link RedisClientConfig#configureClientName()} is enabled, and this property is not set
* it will attempt to extract the value from the {@link RedisClientName#value()} annotation.
* <p>
* If the {@link RedisClientConfig#configureClientName()} is enabled, both this property and the
* {@link RedisClientName#value()} must adhere to the pattern '[a-zA-Z0-9\\-_.~]*'; if not,
* this may result in an incorrect client name after URI encoding.
*/
Optional<String> clientName();

/**
* Whether it should set the client name while connecting with Redis.
* <p>
* This is necessary because Redis only accepts {@code client=my-client-name} query parameter in version 6+.
* <p>
* This property can be used with {@link RedisClientConfig#clientName()} configuration.
*
*/
@WithDefault("false")
Boolean configureClientName();

/**
* The name of the TLS configuration to use.
* <p>
Expand Down Expand Up @@ -232,6 +256,8 @@ default String toDebugString() {
", hashSlotCacheTtl=" + hashSlotCacheTtl() +
", tcp=" + tcp() +
", tls=" + tls() +
", clientName=" + clientName() +
", configureClientName=" + configureClientName() +
'}';
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.quarkus.redis.runtime.client;

import java.net.URI;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

class VertxRedisClientFactoryTest {

@Test
void shouldApplyQuery() {

String applied = VertxRedisClientFactory.applyClientQueryParam(
"quarkus-app", URI.create("redis://localhost:6379"));
Assertions.assertThat(applied).isEqualTo("redis://localhost:6379?client=quarkus-app");
}

@Test
void shouldNotApplyQuery() {
String applied = VertxRedisClientFactory.applyClientQueryParam(
"quarkus-app", URI.create("redis://localhost:6379?client=quarkiverse-app"));
Assertions.assertThat(applied).isEqualTo("redis://localhost:6379?client=quarkiverse-app");
}

@Test
void shouldApplyWithReservedURICharacters() {
String applied = VertxRedisClientFactory.applyClientQueryParam(
"quarkus&%$ app", URI.create("redis://localhost:6379"));
Assertions.assertThat(applied).isEqualTo("redis://localhost:6379?client=quarkus&%25$%20app");
}

@Test
void shouldApplyWithQueryParams() {
String applied = VertxRedisClientFactory.applyClientQueryParam(
"quarkus-app", URI.create("redis://localhost:6379?someQueryParam=123456789"));
Assertions.assertThat(applied).isEqualTo("redis://localhost:6379?someQueryParam=123456789&client=quarkus-app");
}

}

0 comments on commit 9af0f74

Please sign in to comment.