From 9275bf5992d7476ae1f4c5ece864008638aef73e Mon Sep 17 00:00:00 2001 From: Matheus Cruz Date: Sun, 29 Sep 2024 08:31:15 -0300 Subject: [PATCH] Add client parameter when creating Vertx Redis client --- .../client/RedisConfigureClientNameTest.java | 64 +++++++++++++++++++ .../runtime/client/RedisClientRecorder.java | 4 +- .../client/VertxRedisClientFactory.java | 48 ++++++++++++-- .../client/config/RedisClientConfig.java | 25 ++++++++ 4 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 extensions/redis-client/deployment/src/test/java/io/quarkus/redis/deployment/client/RedisConfigureClientNameTest.java diff --git a/extensions/redis-client/deployment/src/test/java/io/quarkus/redis/deployment/client/RedisConfigureClientNameTest.java b/extensions/redis-client/deployment/src/test/java/io/quarkus/redis/deployment/client/RedisConfigureClientNameTest.java new file mode 100644 index 00000000000000..acc1a56e2168a0 --- /dev/null +++ b/extensions/redis-client/deployment/src/test/java/io/quarkus/redis/deployment/client/RedisConfigureClientNameTest.java @@ -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(); + } +} diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/RedisClientRecorder.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/RedisClientRecorder.java index 1aa23cb5b5bfd6..f61db3c607631c 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/RedisClientRecorder.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/RedisClientRecorder.java @@ -13,6 +13,8 @@ import jakarta.enterprise.inject.Instance; import jakarta.enterprise.inject.spi.CDI; +import org.jboss.logging.Logger; + import io.quarkus.redis.client.RedisClient; import io.quarkus.redis.client.reactive.ReactiveRedisClient; import io.quarkus.redis.datasource.ReactiveRedisDataSource; @@ -38,7 +40,7 @@ public class RedisClientRecorder { // Split client and DS recorders - + private static final Logger LOGGER = Logger.getLogger(RedisClientRecorder.class); private final RedisConfig config; private static final Map clients = new HashMap<>(); private static final Map dataSources = new HashMap<>(); diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java index ec2e2fc3702f06..4e7a28b0d19659 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/VertxRedisClientFactory.java @@ -8,10 +8,14 @@ import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configurePfxTrustOptions; import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; 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; @@ -41,6 +45,7 @@ public class VertxRedisClientFactory { public static final String DEFAULT_CLIENT = ""; + public static String NON_RESERVED_URI_PATTERN = "[^a-zA-Z0-9\\-_.~]"; private static final Logger LOGGER = Logger.getLogger(VertxRedisClientFactory.class); @@ -51,19 +56,30 @@ private VertxRedisClientFactory() { public static Redis create(String name, Vertx vertx, RedisClientConfig config, TlsConfigurationRegistry tlsRegistry) { RedisOptions options = new RedisOptions(); + Consumer> configureOptions = new Consumer>() { + @Override + public void accept(Set 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 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 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."); @@ -109,6 +125,26 @@ public static Redis create(String name, Vertx vertx, RedisClientConfig config, T return Redis.createClient(vertx, options); } + private 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 encodedClient = URLEncoder.encode(client, StandardCharsets.UTF_8); + String query = uri.getQuery() == null ? "client=" + encodedClient + : uri.getQuery() + "&client=" + encodedClient; + 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 void customize(String name, RedisOptions options) { if (Arc.container() != null) { List> customizers = Arc.container().listAll(RedisOptionsCustomizer.class); diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/RedisClientConfig.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/RedisClientConfig.java index f07f5681be1efa..a2af43488239c4 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/RedisClientConfig.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/RedisClientConfig.java @@ -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; @@ -197,6 +198,28 @@ public interface RedisClientConfig { @ConfigDocSection TlsConfig tls(); + /** + * The client name used to identify the connection. + *

+ * 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. + *

+ * If the {@link RedisClientConfig#configureClientName()} is enabled, both this property and the + * {@link RedisClientName#value()} must adhere to the pattern '[^a-zA-Z0-9\\-_.~]'. + */ + Optional clientName(); + + /** + * Whether it should set the client name while connecting with Redis. + *

+ * This is necessary because Redis only accepts {@code client=my-client-name} query parameter after version 6+. + *

+ * This property can be used with {@link RedisClientConfig#clientName()} configuration. + * + */ + @WithDefault("false") + Boolean configureClientName(); + /** * The name of the TLS configuration to use. *

@@ -232,6 +255,8 @@ default String toDebugString() { ", hashSlotCacheTtl=" + hashSlotCacheTtl() + ", tcp=" + tcp() + ", tls=" + tls() + + ", clientName=" + clientName() + + ", configureClientName=" + configureClientName() + '}'; }