Skip to content

Commit cba8a19

Browse files
committed
add configuration option for the preferred protocol version to use during handshake
1 parent 86462a9 commit cba8a19

File tree

9 files changed

+199
-2
lines changed

9 files changed

+199
-2
lines changed

src/main/asciidoc/index.adoc

+9-1
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,15 @@ It is important to note that, the reconnect will create a new connection object,
286286

287287
== Protocol Parser
288288

289-
This client supports both `RESP2` and `RESP3` protocols, at the connection handshake time the client will automatically detect which version is supported by the server and use it.
289+
This client supports both `RESP2` and `RESP3` protocols.
290+
By default, the client attempts to negotiate support for `RESP3` at connection handshake time.
291+
292+
It is possible to use the {@link io.vertx.redis.client.RedisOptions#setPreferredProtocolVersion} method to select the preferred version, `RESP2` or `RESP3`:
293+
294+
[source,$lang]
295+
----
296+
{@link examples.RedisExamples#preferredProtocolVersion1}
297+
----
290298

291299
The parser internally creates an "infinite" readable buffer from all the chunks received from the server, in order to avoid creating too much garbage in terms of memory collection, a tunnable watermark value is configurable at JVM startup time.
292300
The system property `io.vertx.redis.parser.watermark` defines how much data is keept in this readable buffer before it gets discarded.

src/main/generated/io/vertx/redis/client/RedisConnectOptionsConverter.java

+8
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ static void fromJson(Iterable<java.util.Map.Entry<String, Object>> json, RedisCo
3030
obj.setProtocolNegotiation((Boolean)member.getValue());
3131
}
3232
break;
33+
case "preferredProtocolVersion":
34+
if (member.getValue() instanceof String) {
35+
obj.setPreferredProtocolVersion(io.vertx.redis.client.ProtocolVersion.valueOf((String)member.getValue()));
36+
}
37+
break;
3338
case "password":
3439
if (member.getValue() instanceof String) {
3540
obj.setPassword((String)member.getValue());
@@ -76,6 +81,9 @@ static void toJson(RedisConnectOptions obj, JsonObject json) {
7681
static void toJson(RedisConnectOptions obj, java.util.Map<String, Object> json) {
7782
json.put("maxNestedArrays", obj.getMaxNestedArrays());
7883
json.put("protocolNegotiation", obj.isProtocolNegotiation());
84+
if (obj.getPreferredProtocolVersion() != null) {
85+
json.put("preferredProtocolVersion", obj.getPreferredProtocolVersion().name());
86+
}
7987
if (obj.getPassword() != null) {
8088
json.put("password", obj.getPassword());
8189
}

src/main/generated/io/vertx/redis/client/RedisOptionsConverter.java

+8
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ static void fromJson(Iterable<java.util.Map.Entry<String, Object>> json, RedisOp
118118
obj.setProtocolNegotiation((Boolean)member.getValue());
119119
}
120120
break;
121+
case "preferredProtocolVersion":
122+
if (member.getValue() instanceof String) {
123+
obj.setPreferredProtocolVersion(io.vertx.redis.client.ProtocolVersion.valueOf((String)member.getValue()));
124+
}
125+
break;
121126
case "poolName":
122127
if (member.getValue() instanceof String) {
123128
obj.setPoolName((String)member.getValue());
@@ -173,6 +178,9 @@ static void toJson(RedisOptions obj, java.util.Map<String, Object> json) {
173178
json.put("password", obj.getPassword());
174179
}
175180
json.put("protocolNegotiation", obj.isProtocolNegotiation());
181+
if (obj.getPreferredProtocolVersion() != null) {
182+
json.put("preferredProtocolVersion", obj.getPreferredProtocolVersion().name());
183+
}
176184
if (obj.getPoolName() != null) {
177185
json.put("poolName", obj.getPoolName());
178186
}

src/main/java/examples/RedisExamples.java

+4
Original file line numberDiff line numberDiff line change
@@ -247,4 +247,8 @@ public void example13(Vertx vertx) {
247247
public void tracing1(RedisOptions options) {
248248
options.setTracingPolicy(TracingPolicy.ALWAYS);
249249
}
250+
251+
public void preferredProtocolVersion1(RedisOptions options) {
252+
options.setPreferredProtocolVersion(ProtocolVersion.RESP2);
253+
}
250254
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.vertx.redis.client;
2+
3+
public enum ProtocolVersion {
4+
RESP2("2"),
5+
RESP3("3"),
6+
;
7+
8+
private final String value;
9+
10+
ProtocolVersion(String value) {
11+
this.value = value;
12+
}
13+
14+
public String getValue() {
15+
return value;
16+
}
17+
}

src/main/java/io/vertx/redis/client/RedisConnectOptions.java

+25
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public abstract class RedisConnectOptions {
3030
private List<String> endpoints;
3131
private int maxNestedArrays;
3232
private boolean protocolNegotiation;
33+
private ProtocolVersion preferredProtocolVersion;
3334
private int maxWaitingHandlers;
3435

3536
private void init() {
@@ -41,6 +42,7 @@ private void init() {
4142
public RedisConnectOptions(RedisOptions options) {
4243
setEndpoints(new ArrayList<>(options.getEndpoints()));
4344
setProtocolNegotiation(options.isProtocolNegotiation());
45+
setPreferredProtocolVersion(options.getPreferredProtocolVersion());
4446
setMaxNestedArrays(options.getMaxNestedArrays());
4547
setPassword(options.getPassword());
4648
setMaxWaitingHandlers(options.getMaxWaitingHandlers());
@@ -55,6 +57,7 @@ public RedisConnectOptions(RedisConnectOptions other) {
5557
init();
5658
this.maxNestedArrays = other.maxNestedArrays;
5759
this.protocolNegotiation = other.protocolNegotiation;
60+
this.preferredProtocolVersion = other.preferredProtocolVersion;
5861
this.password = other.password;
5962
this.endpoints = new ArrayList<>(other.endpoints);
6063
this.maxWaitingHandlers = other.maxWaitingHandlers;
@@ -111,6 +114,28 @@ public RedisConnectOptions setProtocolNegotiation(boolean protocolNegotiation) {
111114
return this;
112115
}
113116

117+
/**
118+
* Returns the preferred protocol version to be used during protocol negotiation. When not set,
119+
* defaults to RESP 3. When protocol negotiation is disabled, this setting has no effect.
120+
*
121+
* @return preferred protocol version
122+
*/
123+
public ProtocolVersion getPreferredProtocolVersion() {
124+
return preferredProtocolVersion;
125+
}
126+
127+
/**
128+
* Sets the preferred protocol version to be used during protocol negotiation. When not set,
129+
* defaults to RESP 3. When protocol negotiation is disabled, this setting has no effect.
130+
*
131+
* @param preferredProtocolVersion preferred protocol version
132+
* @return fluent self
133+
*/
134+
public RedisConnectOptions setPreferredProtocolVersion(ProtocolVersion preferredProtocolVersion) {
135+
this.preferredProtocolVersion = preferredProtocolVersion;
136+
return this;
137+
}
138+
114139
/**
115140
* Get the default password for cluster/sentinel connections, if not set it will try to
116141
* extract it from the current default endpoint.

src/main/java/io/vertx/redis/client/RedisOptions.java

+25
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public class RedisOptions {
5151
private RedisReplicas useReplicas;
5252
private volatile String password;
5353
private boolean protocolNegotiation;
54+
private ProtocolVersion preferredProtocolVersion;
5455
private long hashSlotCacheTTL;
5556
private TracingPolicy tracingPolicy;
5657

@@ -88,6 +89,7 @@ public RedisOptions(RedisOptions other) {
8889
this.useReplicas = other.useReplicas;
8990
this.password = other.password;
9091
this.protocolNegotiation = other.protocolNegotiation;
92+
this.preferredProtocolVersion = other.preferredProtocolVersion;
9193
this.hashSlotCacheTTL = other.hashSlotCacheTTL;
9294
this.maxWaitingHandlers = other.maxWaitingHandlers;
9395
this.tracingPolicy = other.tracingPolicy;
@@ -524,6 +526,29 @@ public RedisOptions setProtocolNegotiation(boolean protocolNegotiation) {
524526
return this;
525527
}
526528

529+
/**
530+
* Returns the preferred protocol version to be used during protocol negotiation. When not set,
531+
* defaults to RESP 3. When protocol negotiation is disabled, this setting has no effect.
532+
*
533+
* @return preferred protocol version
534+
*/
535+
public ProtocolVersion getPreferredProtocolVersion() {
536+
return preferredProtocolVersion;
537+
}
538+
539+
/**
540+
* Sets the preferred protocol version to be used during protocol negotiation. When not set,
541+
* defaults to RESP 3. When protocol negotiation is disabled, this setting has no effect.
542+
*
543+
* @param preferredProtocolVersion preferred protocol version
544+
* @return fluent self
545+
*/
546+
public RedisOptions setPreferredProtocolVersion(ProtocolVersion preferredProtocolVersion) {
547+
this.preferredProtocolVersion = preferredProtocolVersion;
548+
return this;
549+
}
550+
551+
527552
/**
528553
* Set a user defined pool name (for metrics reporting).
529554
*

src/main/java/io/vertx/redis/client/impl/RedisConnectionManager.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,11 @@ private Future<Void> hello(ContextInternal ctx, RedisConnection connection, Redi
224224
if (!options.isProtocolNegotiation()) {
225225
return ping(ctx, connection);
226226
} else {
227-
Request hello = Request.cmd(Command.HELLO).arg(RESPParser.VERSION);
227+
String version = RESPParser.VERSION;
228+
if (options.getPreferredProtocolVersion() != null) {
229+
version = options.getPreferredProtocolVersion().getValue();
230+
}
231+
Request hello = Request.cmd(Command.HELLO).arg(version);
228232

229233
String password = redisURI.password() != null ? redisURI.password() : options.getPassword();
230234
String user = redisURI.user();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package io.vertx.redis.client.test;
2+
3+
import io.vertx.ext.unit.Async;
4+
import io.vertx.ext.unit.TestContext;
5+
import io.vertx.ext.unit.junit.RunTestOnContext;
6+
import io.vertx.ext.unit.junit.VertxUnitRunner;
7+
import io.vertx.redis.client.Command;
8+
import io.vertx.redis.client.ProtocolVersion;
9+
import io.vertx.redis.client.Redis;
10+
import io.vertx.redis.client.RedisOptions;
11+
import io.vertx.redis.client.Request;
12+
import org.junit.ClassRule;
13+
import org.junit.Rule;
14+
import org.junit.Test;
15+
import org.junit.runner.RunWith;
16+
import org.testcontainers.containers.GenericContainer;
17+
18+
@RunWith(VertxUnitRunner.class)
19+
public class RedisProtocolVersionTest {
20+
@ClassRule
21+
public static final GenericContainer<?> redis = new GenericContainer<>("redis:7")
22+
.withExposedPorts(6379);
23+
24+
@Rule
25+
public final RunTestOnContext rule = new RunTestOnContext();
26+
27+
@Test
28+
public void resp2(TestContext test) {
29+
RedisOptions options = new RedisOptions()
30+
.setConnectionString("redis://" + redis.getHost() + ":" + redis.getFirstMappedPort())
31+
.setPreferredProtocolVersion(ProtocolVersion.RESP2);
32+
33+
Redis client = Redis.createClient(rule.vertx(), options);
34+
35+
Async async = test.async();
36+
client
37+
.send(Request.cmd(Command.DEL).arg("myhash"))
38+
.flatMap(ignored -> {
39+
return client.send(Request.cmd(Command.HSET).arg("myhash").arg("field1").arg(1).arg("field2").arg(2));
40+
})
41+
.flatMap(response -> {
42+
test.assertEquals(2, response.toInteger());
43+
return client.send(Request.cmd(Command.HGETALL).arg("myhash"));
44+
})
45+
.onSuccess(response -> {
46+
test.assertTrue(response.toString().startsWith("[")); // list
47+
48+
test.assertTrue(response.containsKey("field1"));
49+
test.assertEquals(1, response.get("field1").toInteger());
50+
51+
test.assertTrue(response.containsKey("field2"));
52+
test.assertEquals(2, response.get("field2").toInteger());
53+
54+
test.assertEquals("field1", response.get(0).toString());
55+
56+
client.close();
57+
async.complete();
58+
}).onFailure(test::fail);
59+
}
60+
61+
@Test
62+
public void resp3(TestContext test) {
63+
RedisOptions options = new RedisOptions()
64+
.setConnectionString("redis://" + redis.getHost() + ":" + redis.getFirstMappedPort())
65+
.setPreferredProtocolVersion(ProtocolVersion.RESP3);
66+
67+
Redis client = Redis.createClient(rule.vertx(), options);
68+
69+
Async async = test.async();
70+
client
71+
.send(Request.cmd(Command.DEL).arg("myhash"))
72+
.flatMap(ignored -> {
73+
return client.send(Request.cmd(Command.HSET).arg("myhash").arg("field1").arg(3).arg("field2").arg(4));
74+
})
75+
.flatMap(response -> {
76+
test.assertEquals(2, response.toInteger());
77+
return client.send(Request.cmd(Command.HGETALL).arg("myhash"));
78+
})
79+
.onSuccess(response -> {
80+
test.assertTrue(response.toString().startsWith("{")); // map
81+
82+
test.assertTrue(response.containsKey("field1"));
83+
test.assertEquals(3, response.get("field1").toInteger());
84+
85+
test.assertTrue(response.containsKey("field2"));
86+
test.assertEquals(4, response.get("field2").toInteger());
87+
88+
try {
89+
response.get(0);
90+
test.fail("Map-typed Multi should fail on get(int)");
91+
} catch (Exception expected) {
92+
}
93+
94+
client.close();
95+
async.complete();
96+
}).onFailure(test::fail);
97+
}
98+
}

0 commit comments

Comments
 (0)