This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new 799dbfe93a refactor: extract shared discovery upstream data handler
base (#6605) (#7487)
799dbfe93a is described below
commit 799dbfe93a4ef3c29616cc2f39c17da1261e6253
Author: wy471x <[email protected]>
AuthorDate: Fri Oct 16 09:43:03 2026 +0800
refactor: extract shared discovery upstream data handler base (#6605)
(#7487)
- Add AbstractDiscoveryUpstreamDataHandler in shenyu-plugin-base
implementing
the common null-check -> convert -> gray-split -> submit flow, mirroring
the
proven AbstractDubboPluginDataHandler pattern.
- Add UpstreamProps, a typed deserializer that centralizes the warmup, gray
and
healthCheckEnabled property names and defaults so they cannot drift
between
plugins.
- Make the divide, websocket and grpc discovery upstream handlers extend the
base and reuse UpstreamProps instead of duplicating the conversion body
with
inline string literals.
- Cover the shared flow, the props parser and the websocket handler with
unit
tests, and extend the grpc handler test with the upstream data path.
---
.../AbstractDiscoveryUpstreamDataHandler.java | 72 ++++++++++
.../shenyu/plugin/base/utils/UpstreamProps.java | 103 +++++++++++++++
.../AbstractDiscoveryUpstreamDataHandlerTest.java | 135 +++++++++++++++++++
.../plugin/base/utils/UpstreamPropsTest.java | 63 +++++++++
.../divide/handler/DivideUpstreamDataHandler.java | 72 +++++-----
.../handler/GrpcDiscoveryUpstreamDataHandler.java | 58 ++++----
.../GrpcDiscoveryUpstreamDataHandlerTest.java | 33 ++++-
.../handler/WebSocketUpstreamDataHandler.java | 61 ++++-----
.../handler/WebSocketUpstreamDataHandlerTest.java | 147 +++++++++++++++++++++
9 files changed, 640 insertions(+), 104 deletions(-)
diff --git
a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/handler/AbstractDiscoveryUpstreamDataHandler.java
b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/handler/AbstractDiscoveryUpstreamDataHandler.java
new file mode 100644
index 0000000000..074bf8fc2a
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/handler/AbstractDiscoveryUpstreamDataHandler.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.plugin.base.handler;
+
+import org.apache.shenyu.common.dto.DiscoverySyncData;
+import org.apache.shenyu.common.dto.DiscoveryUpstreamData;
+import org.apache.shenyu.sync.data.api.DiscoveryUpstreamKey;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * The common discovery upstream data handler.
+ *
+ * <p>Implements the shared null-check, gray-split and submit flow for
discovery upstream data,
+ * so that the concrete handlers only provide their upstream conversion and
cache access.
+ *
+ * @param <T> the upstream type
+ */
+public abstract class AbstractDiscoveryUpstreamDataHandler<T> implements
DiscoveryUpstreamDataHandler {
+
+ @Override
+ public void handlerDiscoveryUpstreamData(final DiscoverySyncData
discoverySyncData) {
+ if (Objects.isNull(discoverySyncData) ||
Objects.isNull(discoverySyncData.getSelectorId())) {
+ return;
+ }
+ final String selectorId = discoverySyncData.getSelectorId();
+ final List<T> upstreamList =
convertUpstreamList(discoverySyncData.getUpstreamDataList());
+ final List<T> grayUpstreamList =
upstreamList.stream().filter(this::isGray).collect(Collectors.toList());
+ if (grayUpstreamList.isEmpty()) {
+ submitUpstreamData(selectorId, upstreamList);
+ } else {
+ submitUpstreamData(selectorId, grayUpstreamList);
+ }
+ afterSubmitUpstreamData(selectorId);
+ }
+
+ @Override
+ public void removeDiscoveryUpstreamData(final DiscoveryUpstreamKey key) {
+ if (Objects.isNull(key) || Objects.isNull(key.selectorId())) {
+ return;
+ }
+ removeUpstreamData(key.selectorId());
+ }
+
+ protected abstract List<T> convertUpstreamList(List<DiscoveryUpstreamData>
upstreamList);
+
+ protected abstract boolean isGray(T upstream);
+
+ protected abstract void submitUpstreamData(String selectorId, List<T>
upstreamList);
+
+ protected abstract void removeUpstreamData(String selectorId);
+
+ protected void afterSubmitUpstreamData(final String selectorId) {
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/utils/UpstreamProps.java
b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/utils/UpstreamProps.java
new file mode 100644
index 0000000000..4161bcc8ed
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/utils/UpstreamProps.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.plugin.base.utils;
+
+import org.apache.shenyu.common.utils.GsonUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Typed view over the raw props payload of a discovery upstream.
+ *
+ * <p>Centralizes the property names and defaults shared by the discovery
upstream handlers,
+ * so that warmup, gray and health check flags cannot drift between plugins.
+ */
+public final class UpstreamProps {
+
+ private static final String WARMUP = "warmup";
+
+ private static final String GRAY = "gray";
+
+ private static final String HEALTH_CHECK_ENABLED = "healthCheckEnabled";
+
+ private static final String DEFAULT_WARMUP = "10";
+
+ private static final String DEFAULT_GRAY = "false";
+
+ private static final String DEFAULT_HEALTH_CHECK_ENABLED = "true";
+
+ private final Map<String, String> props;
+
+ private UpstreamProps(final Map<String, String> props) {
+ this.props = props;
+ }
+
+ /**
+ * Parse the raw props payload of a discovery upstream.
+ *
+ * @param props the raw props json, may be null
+ * @return the parsed props
+ */
+ public static UpstreamProps parse(final String props) {
+ return new UpstreamProps(Optional.ofNullable(props)
+ .map(ps -> GsonUtils.getInstance().toObjectMap(ps,
String.class))
+ .orElseGet(HashMap::new));
+ }
+
+ /**
+ * Get the warmup, {@code 10} by default.
+ *
+ * @return the warmup
+ */
+ public int getWarmup() {
+ return Integer.parseInt(getProperty(WARMUP, DEFAULT_WARMUP));
+ }
+
+ /**
+ * Whether the upstream is a gray upstream, {@code false} by default.
+ *
+ * @return true if the upstream is gray
+ */
+ public boolean isGray() {
+ return Boolean.parseBoolean(getProperty(GRAY, DEFAULT_GRAY));
+ }
+
+ /**
+ * Whether the health check is enabled, {@code true} by default.
+ *
+ * @return true if the health check is enabled
+ */
+ public boolean isHealthCheckEnabled() {
+ return Boolean.parseBoolean(getProperty(HEALTH_CHECK_ENABLED,
DEFAULT_HEALTH_CHECK_ENABLED));
+ }
+
+ /**
+ * Get all parsed props as a mutable map.
+ *
+ * @return a copy of the parsed props
+ */
+ public Map<String, String> toMap() {
+ return new HashMap<>(props);
+ }
+
+ private String getProperty(final String key, final String defaultValue) {
+ return Optional.ofNullable(props.get(key)).orElse(defaultValue);
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/handler/AbstractDiscoveryUpstreamDataHandlerTest.java
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/handler/AbstractDiscoveryUpstreamDataHandlerTest.java
new file mode 100644
index 0000000000..bdb02774fb
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/handler/AbstractDiscoveryUpstreamDataHandlerTest.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.plugin.base.handler;
+
+import org.apache.shenyu.common.dto.DiscoverySyncData;
+import org.apache.shenyu.common.dto.DiscoveryUpstreamData;
+import org.apache.shenyu.sync.data.api.DiscoveryUpstreamKey;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case for {@link AbstractDiscoveryUpstreamDataHandler}.
+ */
+class AbstractDiscoveryUpstreamDataHandlerTest {
+
+ @Test
+ void testIgnoreNullDiscoverySyncData() {
+ final TestHandler handler = new TestHandler();
+
+ handler.handlerDiscoveryUpstreamData(null);
+ final DiscoverySyncData discoverySyncData = new DiscoverySyncData();
+ handler.handlerDiscoveryUpstreamData(discoverySyncData);
+
+ assertTrue(handler.submitted.isEmpty());
+ }
+
+ @Test
+ void testSubmitAllUpstreamsWhenNoGrayUpstream() {
+ final TestHandler handler = new TestHandler();
+
+ handler.handlerDiscoveryUpstreamData(discoverySyncData("a", "b"));
+
+ assertEquals(List.of("a", "b"), handler.submitted);
+ assertEquals(1, handler.afterSubmitCount);
+ }
+
+ @Test
+ void testSubmitOnlyGrayUpstreamsWhenPresent() {
+ final TestHandler handler = new TestHandler();
+ handler.grayUrl = "gray";
+
+ handler.handlerDiscoveryUpstreamData(discoverySyncData("gray",
"normal"));
+
+ assertEquals(List.of("gray"), handler.submitted);
+ }
+
+ @Test
+ void testRemoveDiscoveryUpstreamData() {
+ final TestHandler handler = new TestHandler();
+
+ handler.removeDiscoveryUpstreamData(null);
+ handler.removeDiscoveryUpstreamData(new DiscoveryUpstreamKey("test",
null, null));
+ assertTrue(handler.removed.isEmpty());
+
+ handler.removeDiscoveryUpstreamData(new DiscoveryUpstreamKey("test",
"selector", null));
+ assertEquals(List.of("selector"), handler.removed);
+ }
+
+ private DiscoverySyncData discoverySyncData(final String... urls) {
+ final DiscoverySyncData discoverySyncData = new DiscoverySyncData();
+ discoverySyncData.setSelectorId("selector");
+
discoverySyncData.setUpstreamDataList(Arrays.stream(urls).map(this::upstream).collect(Collectors.toList()));
+ return discoverySyncData;
+ }
+
+ private DiscoveryUpstreamData upstream(final String url) {
+ final DiscoveryUpstreamData upstream = new DiscoveryUpstreamData();
+ upstream.setUrl(url);
+ return upstream;
+ }
+
+ private static final class TestHandler extends
AbstractDiscoveryUpstreamDataHandler<String> {
+
+ private final List<String> submitted = new ArrayList<>();
+
+ private final List<String> removed = new ArrayList<>();
+
+ private String grayUrl;
+
+ private int afterSubmitCount;
+
+ @Override
+ protected List<String> convertUpstreamList(final
List<DiscoveryUpstreamData> upstreamList) {
+ return
upstreamList.stream().map(DiscoveryUpstreamData::getUrl).collect(Collectors.toList());
+ }
+
+ @Override
+ protected boolean isGray(final String upstream) {
+ return Objects.equals(grayUrl, upstream);
+ }
+
+ @Override
+ protected void submitUpstreamData(final String selectorId, final
List<String> upstreamList) {
+ submitted.addAll(upstreamList);
+ }
+
+ @Override
+ protected void afterSubmitUpstreamData(final String selectorId) {
+ afterSubmitCount++;
+ }
+
+ @Override
+ protected void removeUpstreamData(final String selectorId) {
+ removed.add(selectorId);
+ }
+
+ @Override
+ public String pluginName() {
+ return "test";
+ }
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/utils/UpstreamPropsTest.java
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/utils/UpstreamPropsTest.java
new file mode 100644
index 0000000000..4331e29947
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/utils/UpstreamPropsTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.plugin.base.utils;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case for {@link UpstreamProps}.
+ */
+class UpstreamPropsTest {
+
+ @Test
+ void testParseNullPropsAppliesDefaults() {
+ final UpstreamProps props = UpstreamProps.parse(null);
+
+ assertEquals(10, props.getWarmup());
+ assertFalse(props.isGray());
+ assertTrue(props.isHealthCheckEnabled());
+ assertTrue(props.toMap().isEmpty());
+ }
+
+ @Test
+ void testParsePropsOverridesDefaults() {
+ final UpstreamProps props =
UpstreamProps.parse("{\"warmup\":\"20\",\"gray\":\"true\",\"healthCheckEnabled\":\"false\"}");
+
+ assertEquals(20, props.getWarmup());
+ assertTrue(props.isGray());
+ assertFalse(props.isHealthCheckEnabled());
+ }
+
+ @Test
+ void testToMapReturnsCopyOfAllProps() {
+ final UpstreamProps props =
UpstreamProps.parse("{\"warmup\":\"20\",\"az\":\"az1\"}");
+ final Map<String, String> map = props.toMap();
+
+ assertEquals("20", map.get("warmup"));
+ assertEquals("az1", map.get("az"));
+
+ map.put("az", "az2");
+ assertEquals("az1", props.toMap().get("az"));
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/main/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandler.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/main/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandler.java
index 7617a83250..1413785cfc 100644
---
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/main/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandler.java
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/main/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandler.java
@@ -17,78 +17,47 @@
package org.apache.shenyu.plugin.divide.handler;
-import org.apache.shenyu.common.dto.DiscoverySyncData;
import org.apache.shenyu.common.dto.DiscoveryUpstreamData;
import org.apache.shenyu.common.enums.PluginEnum;
-import org.apache.shenyu.common.utils.GsonUtils;
import org.apache.shenyu.loadbalancer.cache.UpstreamCacheManager;
import org.apache.shenyu.loadbalancer.entity.Upstream;
import org.apache.shenyu.plugin.base.cache.MetaDataCache;
-import org.apache.shenyu.plugin.base.handler.DiscoveryUpstreamDataHandler;
-import org.apache.shenyu.sync.data.api.DiscoveryUpstreamKey;
+import
org.apache.shenyu.plugin.base.handler.AbstractDiscoveryUpstreamDataHandler;
+import org.apache.shenyu.plugin.base.utils.UpstreamProps;
import org.springframework.util.ObjectUtils;
import java.sql.Timestamp;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* upstreamList data change.
*/
-public class DivideUpstreamDataHandler implements DiscoveryUpstreamDataHandler
{
-
- @Override
- public void handlerDiscoveryUpstreamData(final DiscoverySyncData
discoverySyncData) {
- if (Objects.isNull(discoverySyncData) ||
Objects.isNull(discoverySyncData.getSelectorId())) {
- return;
- }
- List<DiscoveryUpstreamData> upstreamList =
discoverySyncData.getUpstreamDataList();
- final List<Upstream> upstreams = convertUpstreamList(upstreamList);
- final List<Upstream> grayUpstreamList =
upstreams.stream().filter(Upstream::isGray).toList();
- if (!grayUpstreamList.isEmpty()) {
-
UpstreamCacheManager.getInstance().submit(discoverySyncData.getSelectorId(),
grayUpstreamList);
- } else {
-
UpstreamCacheManager.getInstance().submit(discoverySyncData.getSelectorId(),
upstreams);
- }
- // the update is also need to clean, but there is no way to
- // distinguish between crate and update, so it is always clean
- MetaDataCache.getInstance().clean();
- }
-
- @Override
- public void removeDiscoveryUpstreamData(final DiscoveryUpstreamKey key) {
- if (Objects.isNull(key) || Objects.isNull(key.selectorId())) {
- return;
- }
- UpstreamCacheManager.getInstance().removeByKey(key.selectorId());
- }
+public class DivideUpstreamDataHandler extends
AbstractDiscoveryUpstreamDataHandler<Upstream> {
@Override
public String pluginName() {
return PluginEnum.DIVIDE.getName();
}
- private List<Upstream> convertUpstreamList(final
List<DiscoveryUpstreamData> upstreamList) {
+ @Override
+ protected List<Upstream> convertUpstreamList(final
List<DiscoveryUpstreamData> upstreamList) {
if (ObjectUtils.isEmpty(upstreamList)) {
return Collections.emptyList();
}
return upstreamList.stream().map(u -> {
- Map<String, String> metadata = Optional.ofNullable(u.getProps())
- .map(ps -> GsonUtils.getInstance().toObjectMap(ps,
String.class))
- .map(HashMap::new)
- .orElseGet(HashMap::new);
+ UpstreamProps props = UpstreamProps.parse(u.getProps());
+ Map<String, String> metadata = props.toMap();
Upstream upstream = Upstream.builder()
.protocol(u.getProtocol())
.url(u.getUrl())
.weight(u.getWeight())
- .warmup(Integer.parseInt(metadata.getOrDefault("warmup",
"10")))
- .gray(Boolean.parseBoolean(metadata.getOrDefault("gray",
"false")))
-
.healthCheckEnabled(Boolean.parseBoolean(metadata.getOrDefault("healthCheckEnabled",
"true")))
+ .warmup(props.getWarmup())
+ .gray(props.isGray())
+ .healthCheckEnabled(props.isHealthCheckEnabled())
.status(0 == u.getStatus())
.timestamp(Optional.ofNullable(u.getDateCreated()).map(Timestamp::getTime).orElse(System.currentTimeMillis()))
.build();
@@ -97,4 +66,25 @@ public class DivideUpstreamDataHandler implements
DiscoveryUpstreamDataHandler {
}).collect(Collectors.toList());
}
+ @Override
+ protected boolean isGray(final Upstream upstream) {
+ return upstream.isGray();
+ }
+
+ @Override
+ protected void submitUpstreamData(final String selectorId, final
List<Upstream> upstreamList) {
+ UpstreamCacheManager.getInstance().submit(selectorId, upstreamList);
+ }
+
+ @Override
+ protected void afterSubmitUpstreamData(final String selectorId) {
+ // the update is also need to clean, but there is no way to
+ // distinguish between crate and update, so it is always clean
+ MetaDataCache.getInstance().clean();
+ }
+
+ @Override
+ protected void removeUpstreamData(final String selectorId) {
+ UpstreamCacheManager.getInstance().removeByKey(selectorId);
+ }
}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandler.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandler.java
index 9af925d983..bbfb6c017c 100644
---
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandler.java
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandler.java
@@ -21,12 +21,11 @@ import org.apache.shenyu.common.dto.DiscoverySyncData;
import org.apache.shenyu.common.dto.DiscoveryUpstreamData;
import org.apache.shenyu.common.dto.convert.selector.GrpcUpstream;
import org.apache.shenyu.common.enums.PluginEnum;
-import org.apache.shenyu.common.utils.GsonUtils;
import org.apache.shenyu.common.utils.JsonUtils;
-import org.apache.shenyu.plugin.base.handler.DiscoveryUpstreamDataHandler;
+import
org.apache.shenyu.plugin.base.handler.AbstractDiscoveryUpstreamDataHandler;
+import org.apache.shenyu.plugin.base.utils.UpstreamProps;
import org.apache.shenyu.plugin.grpc.cache.ApplicationConfigCache;
import org.apache.shenyu.plugin.grpc.cache.GrpcClientCache;
-import org.apache.shenyu.sync.data.api.DiscoveryUpstreamKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;
@@ -36,60 +35,63 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.Properties;
import java.util.stream.Collectors;
/**
* GrpcDiscoveryUpstreamDataHandler.
*/
-public class GrpcDiscoveryUpstreamDataHandler implements
DiscoveryUpstreamDataHandler {
-
+public class GrpcDiscoveryUpstreamDataHandler extends
AbstractDiscoveryUpstreamDataHandler<GrpcUpstream> {
+
private static final Logger LOG =
LoggerFactory.getLogger(GrpcDiscoveryUpstreamDataHandler.class);
@Override
public void handlerDiscoveryUpstreamData(final DiscoverySyncData
discoverySyncData) {
- if (Objects.isNull(discoverySyncData) ||
Objects.isNull(discoverySyncData.getSelectorId())) {
- return;
- }
- LOG.info("discovery grpc upstream data:{}",
JsonUtils.toJson(discoverySyncData));
- final String selectorId = discoverySyncData.getSelectorId();
- final List<GrpcUpstream> upstreams =
convertUpstreamList(discoverySyncData.getUpstreamDataList());
- final List<GrpcUpstream> grayUpstreamList =
upstreams.stream().filter(GrpcUpstream::isGray).toList();
- if (!grayUpstreamList.isEmpty()) {
-
ApplicationConfigCache.getInstance().handlerUpstream(discoverySyncData.getSelectorId(),
grayUpstreamList);
- } else {
-
ApplicationConfigCache.getInstance().handlerUpstream(discoverySyncData.getSelectorId(),
upstreams);
+ if (Objects.nonNull(discoverySyncData) &&
Objects.nonNull(discoverySyncData.getSelectorId())) {
+ LOG.info("discovery grpc upstream data:{}",
JsonUtils.toJson(discoverySyncData));
}
- GrpcClientCache.initGrpcClient(selectorId);
+ super.handlerDiscoveryUpstreamData(discoverySyncData);
}
@Override
- public void removeDiscoveryUpstreamData(final DiscoveryUpstreamKey key) {
- if (Objects.isNull(key) || Objects.isNull(key.selectorId())) {
- return;
- }
- ApplicationConfigCache.getInstance().invalidate(key.selectorId());
+ public String pluginName() {
+ return PluginEnum.GRPC.getName();
}
- private List<GrpcUpstream> convertUpstreamList(final
List<DiscoveryUpstreamData> upstreamList) {
+ @Override
+ protected List<GrpcUpstream> convertUpstreamList(final
List<DiscoveryUpstreamData> upstreamList) {
if (ObjectUtils.isEmpty(upstreamList)) {
return Collections.emptyList();
}
return upstreamList.stream().map(u -> {
- Properties properties = Optional.ofNullable(u.getProps()).map(ps
-> GsonUtils.getInstance().fromJson(ps, Properties.class)).orElse(new
Properties());
+ UpstreamProps props = UpstreamProps.parse(u.getProps());
return GrpcUpstream.builder()
.protocol(u.getProtocol())
.upstreamUrl(u.getUrl())
.weight(u.getWeight())
.status(0 == u.getStatus())
.timestamp(Optional.ofNullable(u.getDateCreated()).map(Timestamp::getTime).orElse(System.currentTimeMillis()))
-
.healthCheckEnabled(Boolean.parseBoolean(properties.getProperty("healthCheckEnabled",
"true")))
+ .healthCheckEnabled(props.isHealthCheckEnabled())
.build();
}).collect(Collectors.toList());
}
@Override
- public String pluginName() {
- return PluginEnum.GRPC.getName();
+ protected boolean isGray(final GrpcUpstream upstream) {
+ return upstream.isGray();
+ }
+
+ @Override
+ protected void submitUpstreamData(final String selectorId, final
List<GrpcUpstream> upstreamList) {
+ ApplicationConfigCache.getInstance().handlerUpstream(selectorId,
upstreamList);
+ }
+
+ @Override
+ protected void afterSubmitUpstreamData(final String selectorId) {
+ GrpcClientCache.initGrpcClient(selectorId);
+ }
+
+ @Override
+ protected void removeUpstreamData(final String selectorId) {
+ ApplicationConfigCache.getInstance().invalidate(selectorId);
}
}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandlerTest.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandlerTest.java
index 90b8421281..db886a9001 100644
---
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandlerTest.java
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandlerTest.java
@@ -17,11 +17,20 @@
package org.apache.shenyu.plugin.grpc.handler;
+import org.apache.shenyu.common.dto.DiscoverySyncData;
+import org.apache.shenyu.common.dto.DiscoveryUpstreamData;
+import org.apache.shenyu.common.dto.convert.selector.GrpcUpstream;
import org.apache.shenyu.sync.data.api.DiscoveryUpstreamKey;
+import org.apache.shenyu.plugin.grpc.cache.ApplicationConfigCache;
import org.apache.shenyu.plugin.grpc.cache.GrpcClientCache;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -31,7 +40,29 @@ public final class GrpcDiscoveryUpstreamDataHandlerTest {
@AfterEach
public void tearDown() {
- GrpcClientCache.removeClient(SELECTOR_ID);
+ ApplicationConfigCache.getInstance().invalidate(SELECTOR_ID);
+ }
+
+ @Test
+ public void testHandlerDiscoveryUpstreamData() {
+ DiscoveryUpstreamData discoveryUpstreamData =
DiscoveryUpstreamData.builder()
+ .protocol("grpc://")
+ .url("127.0.0.1:9090")
+ .weight(100)
+ .status(0)
+ .props("{\"healthCheckEnabled\":\"false\"}")
+ .build();
+ DiscoverySyncData discoverySyncData = new DiscoverySyncData();
+ discoverySyncData.setSelectorId(SELECTOR_ID);
+
discoverySyncData.setUpstreamDataList(Collections.singletonList(discoveryUpstreamData));
+
+ new
GrpcDiscoveryUpstreamDataHandler().handlerDiscoveryUpstreamData(discoverySyncData);
+
+ List<GrpcUpstream> upstreamList =
ApplicationConfigCache.getInstance().getGrpcUpstreamListCache(SELECTOR_ID);
+ assertEquals(1, upstreamList.size());
+ assertEquals("grpc://", upstreamList.get(0).getProtocol());
+ assertFalse(upstreamList.get(0).isHealthCheckEnabled());
+ assertNotNull(GrpcClientCache.getGrpcClient(SELECTOR_ID));
}
@Test
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/main/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandler.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/main/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandler.java
index 5619f18e52..b0285ec7da 100644
---
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/main/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandler.java
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/main/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandler.java
@@ -17,66 +17,44 @@
package org.apache.shenyu.plugin.websocket.handler;
-import org.apache.shenyu.common.dto.DiscoverySyncData;
import org.apache.shenyu.common.dto.DiscoveryUpstreamData;
import org.apache.shenyu.common.enums.PluginEnum;
-import org.apache.shenyu.common.utils.GsonUtils;
import org.apache.shenyu.loadbalancer.cache.UpstreamCacheManager;
import org.apache.shenyu.loadbalancer.entity.Upstream;
import org.apache.shenyu.plugin.base.cache.MetaDataCache;
-import org.apache.shenyu.plugin.base.handler.DiscoveryUpstreamDataHandler;
-import org.apache.shenyu.sync.data.api.DiscoveryUpstreamKey;
+import
org.apache.shenyu.plugin.base.handler.AbstractDiscoveryUpstreamDataHandler;
+import org.apache.shenyu.plugin.base.utils.UpstreamProps;
import org.springframework.util.ObjectUtils;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
-import java.util.Properties;
import java.util.stream.Collectors;
-/*
+/**
* upstreamList data change.
*/
-public class WebSocketUpstreamDataHandler implements
DiscoveryUpstreamDataHandler {
+public class WebSocketUpstreamDataHandler extends
AbstractDiscoveryUpstreamDataHandler<Upstream> {
@Override
- public void handlerDiscoveryUpstreamData(final DiscoverySyncData
discoverySyncData) {
- if (Objects.isNull(discoverySyncData) ||
Objects.isNull(discoverySyncData.getSelectorId())) {
- return;
- }
- List<DiscoveryUpstreamData> upstreamList =
discoverySyncData.getUpstreamDataList();
- final List<Upstream> upstreams = convertUpstreamList(upstreamList);
- final List<Upstream> grayUpstreamList =
upstreams.stream().filter(Upstream::isGray).toList();
- if (!grayUpstreamList.isEmpty()) {
-
UpstreamCacheManager.getInstance().submit(discoverySyncData.getSelectorId(),
grayUpstreamList);
- } else {
-
UpstreamCacheManager.getInstance().submit(discoverySyncData.getSelectorId(),
upstreams);
- }
- MetaDataCache.getInstance().clean();
+ public String pluginName() {
+ return PluginEnum.WEB_SOCKET.getName();
}
@Override
- public void removeDiscoveryUpstreamData(final DiscoveryUpstreamKey key) {
- if (Objects.isNull(key) || Objects.isNull(key.selectorId())) {
- return;
- }
- UpstreamCacheManager.getInstance().removeByKey(key.selectorId());
- }
-
- private List<Upstream> convertUpstreamList(final
List<DiscoveryUpstreamData> upstreamList) {
+ protected List<Upstream> convertUpstreamList(final
List<DiscoveryUpstreamData> upstreamList) {
if (ObjectUtils.isEmpty(upstreamList)) {
return Collections.emptyList();
}
return upstreamList.stream().map(u -> {
- Properties properties = Optional.ofNullable(u.getProps()).map(ps
-> GsonUtils.getInstance().fromJson(ps, Properties.class)).orElse(new
Properties());
+ UpstreamProps props = UpstreamProps.parse(u.getProps());
return Upstream.builder()
.protocol(u.getProtocol())
.url(u.getUrl())
.weight(u.getWeight())
- .warmup(Integer.parseInt(properties.getProperty("warmup",
"10")))
-
.healthCheckEnabled(Boolean.parseBoolean(properties.getProperty("healthCheckEnabled",
"true")))
+ .warmup(props.getWarmup())
+ .healthCheckEnabled(props.isHealthCheckEnabled())
.status(0 == u.getStatus())
.timestamp(Optional.ofNullable(u.getDateCreated()).map(Timestamp::getTime).orElse(System.currentTimeMillis()))
.build();
@@ -84,7 +62,22 @@ public class WebSocketUpstreamDataHandler implements
DiscoveryUpstreamDataHandle
}
@Override
- public String pluginName() {
- return PluginEnum.WEB_SOCKET.getName();
+ protected boolean isGray(final Upstream upstream) {
+ return upstream.isGray();
+ }
+
+ @Override
+ protected void submitUpstreamData(final String selectorId, final
List<Upstream> upstreamList) {
+ UpstreamCacheManager.getInstance().submit(selectorId, upstreamList);
+ }
+
+ @Override
+ protected void afterSubmitUpstreamData(final String selectorId) {
+ MetaDataCache.getInstance().clean();
+ }
+
+ @Override
+ protected void removeUpstreamData(final String selectorId) {
+ UpstreamCacheManager.getInstance().removeByKey(selectorId);
}
}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/test/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandlerTest.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/test/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandlerTest.java
new file mode 100644
index 0000000000..835c6abaac
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/test/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandlerTest.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.plugin.websocket.handler;
+
+import org.apache.shenyu.common.dto.DiscoverySyncData;
+import org.apache.shenyu.common.dto.DiscoveryUpstreamData;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.common.utils.UpstreamCheckUtils;
+import org.apache.shenyu.loadbalancer.cache.UpstreamCacheManager;
+import org.apache.shenyu.loadbalancer.entity.Upstream;
+import org.apache.shenyu.sync.data.api.DiscoveryUpstreamKey;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.Properties;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test case for {@link WebSocketUpstreamDataHandler}.
+ */
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+public class WebSocketUpstreamDataHandlerTest {
+
+ private DiscoverySyncData discoverySyncData;
+
+ private WebSocketUpstreamDataHandler webSocketUpstreamDataHandler;
+
+ private MockedStatic<UpstreamCheckUtils> mockCheckUtils;
+
+ @BeforeEach
+ public void setUp() {
+ this.webSocketUpstreamDataHandler = new WebSocketUpstreamDataHandler();
+ List<DiscoveryUpstreamData> upstreamList = Stream.of(3)
+ .map(weight -> DiscoveryUpstreamData.builder()
+ .url("mock-" + weight)
+ .dateUpdated(new Timestamp(System.currentTimeMillis()))
+ .build())
+ .collect(Collectors.toList());
+ this.discoverySyncData = mock(DiscoverySyncData.class);
+ when(discoverySyncData.getSelectorId()).thenReturn("handler");
+ when(discoverySyncData.getUpstreamDataList()).thenReturn(upstreamList);
+
+ // mock static
+ mockCheckUtils = mockStatic(UpstreamCheckUtils.class);
+ mockCheckUtils.when(() -> UpstreamCheckUtils.checkUrl(anyString(),
anyInt())).thenReturn(true);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ UpstreamCacheManager.getInstance().removeByKey("handler");
+ mockCheckUtils.close();
+ }
+
+ /**
+ * Handler discovery upstream data test.
+ */
+ @Test
+ public void handlerDiscoveryUpstreamDataTest() {
+
webSocketUpstreamDataHandler.handlerDiscoveryUpstreamData(discoverySyncData);
+ List<Upstream> result =
UpstreamCacheManager.getInstance().findUpstreamListBySelectorId("handler");
+ assertEquals(discoverySyncData.getUpstreamDataList().get(0).getUrl(),
result.get(0).getUrl());
+ DiscoverySyncData emptySyncData = new DiscoverySyncData();
+ emptySyncData.setSelectorId(null);
+
webSocketUpstreamDataHandler.handlerDiscoveryUpstreamData(emptySyncData);
+ }
+
+ /**
+ * Handler discovery upstream props test.
+ */
+ @Test
+ public void handlerDiscoveryUpstreamDataWithPropsTest() {
+ Properties properties = new Properties();
+ properties.setProperty("warmup", "20");
+ properties.setProperty("healthCheckEnabled", "false");
+ DiscoveryUpstreamData discoveryUpstreamData =
DiscoveryUpstreamData.builder()
+ .url("mock-props")
+ .status(0)
+ .weight(10)
+ .props(GsonUtils.getInstance().toJson(properties))
+ .dateCreated(new Timestamp(System.currentTimeMillis()))
+ .build();
+
when(discoverySyncData.getUpstreamDataList()).thenReturn(List.of(discoveryUpstreamData));
+
+
webSocketUpstreamDataHandler.handlerDiscoveryUpstreamData(discoverySyncData);
+
+ Upstream upstream =
UpstreamCacheManager.getInstance().findUpstreamListBySelectorId("handler").get(0);
+ assertEquals(20, upstream.getWarmup());
+ assertFalse(upstream.isHealthCheckEnabled());
+ }
+
+ /**
+ * Remove discovery upstream data test.
+ */
+ @Test
+ public void removeDiscoveryUpstreamDataTest() {
+
webSocketUpstreamDataHandler.handlerDiscoveryUpstreamData(discoverySyncData);
+
assertNotNull(UpstreamCacheManager.getInstance().findUpstreamListBySelectorId("handler"));
+
+
webSocketUpstreamDataHandler.removeDiscoveryUpstreamData(DiscoveryUpstreamKey.from(discoverySyncData));
+
+
assertNull(UpstreamCacheManager.getInstance().findUpstreamListBySelectorId("handler"));
+ }
+
+ /**
+ * Plugin name test.
+ */
+ @Test
+ public void pluginNameTest() {
+ assertEquals(PluginEnum.WEB_SOCKET.getName(),
webSocketUpstreamDataHandler.pluginName());
+ }
+}