20
20
import java .time .Duration ;
21
21
import java .util .*;
22
22
import java .util .concurrent .TimeUnit ;
23
- import java .util .regex .Matcher ;
24
- import java .util .regex .Pattern ;
25
23
26
24
import org .apache .commons .logging .Log ;
27
25
import org .apache .commons .logging .LogFactory ;
26
+
28
27
import org .springframework .core .convert .converter .Converter ;
29
28
import org .springframework .data .geo .Distance ;
30
29
import org .springframework .data .geo .GeoResult ;
45
44
import org .springframework .data .redis .connection .zset .Tuple ;
46
45
import org .springframework .data .redis .serializer .RedisSerializer ;
47
46
import org .springframework .data .redis .util .ByteUtils ;
47
+ import org .springframework .lang .NonNull ;
48
48
import org .springframework .lang .Nullable ;
49
49
import org .springframework .util .Assert ;
50
50
import org .springframework .util .ClassUtils ;
@@ -545,10 +545,15 @@ enum ClusterNodesConverter implements Converter<String, RedisClusterNode> {
545
545
* <li>{@code %s:%i} (Redis 3)</li>
546
546
* <li>{@code %s:%i@%i} (Redis 4, with bus port)</li>
547
547
* <li>{@code %s:%i@%i,%s} (Redis 7, with announced hostname)</li>
548
+ *
549
+ * The output of the {@code CLUSTER NODES } command is just a space-separated CSV string, where each
550
+ * line represents a node in the cluster. The following is an example of output on Redis 7.2.0.
551
+ * You can check the latest <a href="https://redis.io/docs/latest/commands/cluster-nodes/">here</a>.
552
+ *
553
+ * {@code <id> <ip:port@cport[,hostname]> <flags> <master> <ping-sent> <pong-recv> <config-epoch> <link-state> <slot> <slot> ... <slot>}
554
+ *
548
555
* </ul>
549
556
*/
550
- static final Pattern clusterEndpointPattern = Pattern
551
- .compile ("\\ [?([0-9a-zA-Z\\ -_\\ .:]*)\\ ]?:([0-9]+)(?:@[0-9]+(?:,([^,].*))?)?" );
552
557
private static final Map <String , Flag > flagLookupMap ;
553
558
554
559
static {
@@ -567,18 +572,75 @@ enum ClusterNodesConverter implements Converter<String, RedisClusterNode> {
567
572
static final int LINK_STATE_INDEX = 7 ;
568
573
static final int SLOTS_INDEX = 8 ;
569
574
575
+ record AddressPortHostname (String addressPart , String portPart , @ Nullable String hostnamePart ) {
576
+
577
+ static AddressPortHostname of (String [] args ) {
578
+ Assert .isTrue (args .length >= HOST_PORT_INDEX + 1 , "ClusterNode information does not define host and port" );
579
+ // <ip:port@cport[,hostname]>
580
+ String hostPort = args [HOST_PORT_INDEX ];
581
+ int lastColon = hostPort .lastIndexOf (":" );
582
+ Assert .isTrue (lastColon != -1 , "ClusterNode information does not define host and port" );
583
+ String addressPart = getAddressPart (hostPort , lastColon );
584
+ // Everything to the right of port
585
+ int indexOfColon = hostPort .indexOf ("," );
586
+ boolean hasColon = indexOfColon != -1 ;
587
+ String hostnamePart = getHostnamePart (hasColon , hostPort , indexOfColon );
588
+ String portPart = getPortPart (hostPort , lastColon , hasColon , indexOfColon );
589
+ return new AddressPortHostname (addressPart , portPart , hostnamePart );
590
+ }
591
+
592
+ @ NonNull private static String getAddressPart (String hostPort , int lastColon ) {
593
+ // Everything to the left of port
594
+ // 127.0.0.1:6380
595
+ // 127.0.0.1:6380@6381
596
+ // :6380
597
+ // :6380@6381
598
+ // 2a02:6b8:c67:9c:0:6d8b:33da:5a2c:6380
599
+ // 2a02:6b8:c67:9c:0:6d8b:33da:5a2c:6380@6381
600
+ // 127.0.0.1:6380,hostname1
601
+ // 127.0.0.1:6380@6381,hostname1
602
+ // :6380,hostname1
603
+ // :6380@6381,hostname1
604
+ // 2a02:6b8:c67:9c:0:6d8b:33da:5a2c:6380,hostname1
605
+ // 2a02:6b8:c67:9c:0:6d8b:33da:5a2c:6380@6381,hostname1
606
+ String addressPart = hostPort .substring (0 , lastColon );
607
+ // [2a02:6b8:c67:9c:0:6d8b:33da:5a2c]:6380
608
+ // [2a02:6b8:c67:9c:0:6d8b:33da:5a2c]:6380@6381
609
+ // [2a02:6b8:c67:9c:0:6d8b:33da:5a2c]:6380,hostname1
610
+ // [2a02:6b8:c67:9c:0:6d8b:33da:5a2c]:6380@6381,hostname1
611
+ if (addressPart .startsWith ("[" ) && addressPart .endsWith ("]" )) {
612
+ addressPart = addressPart .substring (1 , addressPart .length () - 1 );
613
+ }
614
+ return addressPart ;
615
+ }
616
+
617
+ @ Nullable
618
+ private static String getHostnamePart (boolean hasColon , String hostPort , int indexOfColon ) {
619
+ // Everything to the right starting from comma
620
+ String hostnamePart = hasColon ? hostPort .substring (indexOfColon + 1 ) : null ;
621
+ return StringUtils .hasText (hostnamePart ) ? hostnamePart : null ;
622
+ }
623
+
624
+ @ NonNull private static String getPortPart (String hostPort , int lastColon , boolean hasColon , int indexOfColon ) {
625
+ String portPart = hostPort .substring (lastColon + 1 );
626
+ if (portPart .contains ("@" )) {
627
+ portPart = portPart .substring (0 , portPart .indexOf ("@" ));
628
+ } else if (hasColon ) {
629
+ portPart = portPart .substring (0 , indexOfColon );
630
+ }
631
+ return portPart ;
632
+ }
633
+ }
634
+
570
635
@ Override
571
636
public RedisClusterNode convert (String source ) {
572
637
573
638
String [] args = source .split (" " );
574
639
575
- Matcher matcher = clusterEndpointPattern .matcher (args [HOST_PORT_INDEX ]);
576
-
577
- Assert .isTrue (matcher .matches (), "ClusterNode information does not define host and port" );
578
-
579
- String addressPart = matcher .group (1 );
580
- String portPart = matcher .group (2 );
581
- String hostnamePart = matcher .group (3 );
640
+ AddressPortHostname addressPortHostname = AddressPortHostname .of (args );
641
+ String addressPart = addressPortHostname .addressPart ;
642
+ String portPart = addressPortHostname .portPart ;
643
+ String hostnamePart = addressPortHostname .hostnamePart ;
582
644
583
645
SlotRange range = parseSlotRange (args );
584
646
Set <Flag > flags = parseFlags (args );
0 commit comments