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 4d33568dda perf(grpc): reuse parsed rpcExt metadata across requests 
(#7831) (#7841)
4d33568dda is described below

commit 4d33568dda6bf40c6ce15f64340126fb50a69a54
Author: Harsh Raj <[email protected]>
AuthorDate: Mon Oct 19 13:21:18 2026 +0530

    perf(grpc): reuse parsed rpcExt metadata across requests (#7831) (#7841)
    
    Co-authored-by: Harsh <[email protected]>
---
 .../org/apache/shenyu/plugin/grpc/GrpcPlugin.java  |  39 +------
 .../plugin/grpc/cache/ApplicationConfigCache.java  | 127 ++++++++++++++++++++-
 .../shenyu/plugin/grpc/cache/GrpcExtInfo.java      | 102 +++++++++++++++++
 .../plugin/grpc/handler/GrpcMetaDataHandler.java   |  49 ++++++++
 .../apache/shenyu/plugin/grpc/GrpcPluginTest.java  |  24 ++++
 .../grpc/cache/ApplicationConfigCacheTest.java     |  77 +++++++++++++
 .../grpc/handler/GrpcMetaDataHandlerTest.java      |  76 ++++++++++++
 .../plugin/grpc/GrpcPluginConfiguration.java       |  12 ++
 .../plugin/grpc/GrpcPluginConfigurationTest.java   |  10 ++
 9 files changed, 480 insertions(+), 36 deletions(-)

diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/GrpcPlugin.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/GrpcPlugin.java
index 2ff62f367a..d68910c9a8 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/GrpcPlugin.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/GrpcPlugin.java
@@ -19,7 +19,6 @@ package org.apache.shenyu.plugin.grpc;
 
 import io.grpc.CallOptions;
 import io.grpc.Context;
-import io.grpc.MethodDescriptor;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.shenyu.common.constant.Constants;
 import org.apache.shenyu.common.dto.MetaData;
@@ -29,7 +28,6 @@ import org.apache.shenyu.common.enums.PluginEnum;
 import org.apache.shenyu.common.enums.ResultEnum;
 import org.apache.shenyu.common.enums.RpcTypeEnum;
 import org.apache.shenyu.common.exception.ShenyuException;
-import org.apache.shenyu.common.utils.GsonUtils;
 import org.apache.shenyu.plugin.api.ShenyuPluginChain;
 import org.apache.shenyu.plugin.api.context.ShenyuContext;
 import org.apache.shenyu.plugin.api.result.ShenyuResultEnum;
@@ -37,7 +35,9 @@ import org.apache.shenyu.plugin.api.result.ShenyuResultWrap;
 import org.apache.shenyu.plugin.api.utils.RequestUrlUtils;
 import org.apache.shenyu.plugin.api.utils.WebFluxResultUtils;
 import org.apache.shenyu.plugin.base.AbstractShenyuPlugin;
+import org.apache.shenyu.plugin.grpc.cache.ApplicationConfigCache;
 import org.apache.shenyu.plugin.grpc.cache.GrpcClientCache;
+import org.apache.shenyu.plugin.grpc.cache.GrpcExtInfo;
 import org.apache.shenyu.plugin.grpc.client.ShenyuGrpcClient;
 import org.apache.shenyu.plugin.grpc.context.GrpcConstants;
 import org.apache.shenyu.plugin.grpc.proto.ShenyuGrpcResponse;
@@ -104,15 +104,12 @@ public class GrpcPlugin extends AbstractShenyuPlugin {
                         .map(InetAddress::getHostAddress)
                         .orElse(StringUtils.EMPTY)).attach();
 
-        GrpcExtInfo extInfo = StringUtils.isBlank(metaData.getRpcExt())
-                ? new GrpcExtInfo()
-                : 
Optional.ofNullable(GsonUtils.getGson().fromJson(metaData.getRpcExt(), 
GrpcExtInfo.class))
-                        .orElseGet(GrpcExtInfo::new);
-        CallOptions callOptions = 
CallOptions.DEFAULT.withDeadlineAfter(extInfo.timeout, TimeUnit.MILLISECONDS);
+        GrpcExtInfo extInfo = 
ApplicationConfigCache.getInstance().obtainGrpcExtInfo(metaData);
+        CallOptions callOptions = 
CallOptions.DEFAULT.withDeadlineAfter(extInfo.getTimeout(), 
TimeUnit.MILLISECONDS);
         Map<String, Map<String, String>> rpcContext = 
exchange.getAttribute(Constants.GENERAL_CONTEXT);
         Optional.ofNullable(rpcContext).map(context -> 
context.get(PluginEnum.GRPC.getName())).ifPresent(
             context -> Context.current().withValue(RPC_CONTEXT_KEY, 
context).attach());
-        CompletableFuture<ShenyuGrpcResponse> result = client.call(metaData, 
callOptions, param, extInfo.methodType);
+        CompletableFuture<ShenyuGrpcResponse> result = client.call(metaData, 
callOptions, param, extInfo.getMethodType());
         Context.current().detach(Context.ROOT);
 
         return Mono.fromFuture(result.thenApply(ret -> {
@@ -163,30 +160,4 @@ public class GrpcPlugin extends AbstractShenyuPlugin {
     protected Mono<Void> handleRuleIfNull(final String pluginName, final 
ServerWebExchange exchange, final ShenyuPluginChain chain) {
         return WebFluxResultUtils.noRuleResult(pluginName, exchange);
     }
-
-    /**
-     * The GrpcExt.
-     */
-    static class GrpcExtInfo {
-
-        private Integer timeout = 5000;
-
-        private MethodDescriptor.MethodType methodType = 
MethodDescriptor.MethodType.UNARY;
-
-        public Integer getTimeout() {
-            return timeout;
-        }
-
-        public void setTimeout(final Integer timeout) {
-            this.timeout = timeout;
-        }
-
-        public MethodDescriptor.MethodType getMethodType() {
-            return methodType;
-        }
-
-        public void setMethodType(final MethodDescriptor.MethodType 
methodType) {
-            this.methodType = methodType;
-        }
-    }
 }
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/cache/ApplicationConfigCache.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/cache/ApplicationConfigCache.java
index bfd92f0a77..535b37f0a9 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/cache/ApplicationConfigCache.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/cache/ApplicationConfigCache.java
@@ -17,11 +17,15 @@
 
 package org.apache.shenyu.plugin.grpc.cache;
 
+import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
+import io.grpc.MethodDescriptor;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.shenyu.common.constant.Constants;
+import org.apache.shenyu.common.dto.MetaData;
 import org.apache.shenyu.common.dto.convert.rule.impl.GrpcRuleHandle;
 import org.apache.shenyu.common.dto.convert.selector.GrpcUpstream;
 import org.apache.shenyu.common.exception.ShenyuException;
@@ -30,8 +34,9 @@ import org.apache.shenyu.plugin.base.cache.CommonHandleCache;
 import org.apache.shenyu.plugin.base.utils.BeanHolder;
 import org.apache.shenyu.plugin.base.utils.CacheKeyUtils;
 import org.apache.shenyu.plugin.grpc.resolver.ShenyuServiceInstanceLists;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.lang.NonNull;
-import org.springframework.util.StringUtils;
 
 import java.util.List;
 import java.util.Map;
@@ -46,6 +51,10 @@ import java.util.function.Supplier;
  */
 public final class ApplicationConfigCache {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(ApplicationConfigCache.class);
+
+    private static final GrpcExtInfo DEFAULT_EXT_INFO = new GrpcExtInfo(5000, 
MethodDescriptor.MethodType.UNARY);
+
     private final Supplier<CommonHandleCache<String, List<GrpcUpstream>>> 
grpcUpstreamCachedHandle = new BeanHolder<>(CommonHandleCache::new);
 
     private final Supplier<CommonHandleCache<String, GrpcRuleHandle>> 
ruleCachedHandle = new BeanHolder<>(CommonHandleCache::new);
@@ -60,6 +69,10 @@ public final class ApplicationConfigCache {
                 }
             });
 
+    private final Cache<String, CachedGrpcExtInfo> grpcExtCache = 
CacheBuilder.newBuilder()
+            .maximumSize(Constants.CACHE_MAX_COUNT)
+            .build();
+
     private final Map<String, Consumer<Object>> watchUpstreamListener = new 
ConcurrentHashMap<>();
 
     private ApplicationConfigCache() {
@@ -136,7 +149,7 @@ public final class ApplicationConfigCache {
      * @param ruleHandle ruleHandle
      */
     public void cacheRuleHandle(final String ruleDataKey, final String 
ruleHandle) {
-        final String handler = StringUtils.hasText(ruleHandle) ? ruleHandle : 
"{}";
+        final String handler = StringUtils.isNotBlank(ruleHandle) ? ruleHandle 
: "{}";
         ruleCachedHandle.get().cachedHandle(ruleDataKey, 
GsonUtils.getInstance().fromJson(handler, GrpcRuleHandle.class));
     }
 
@@ -169,6 +182,116 @@ public final class ApplicationConfigCache {
         return grpcUpstreamCachedHandle.get().obtainHandle(selectorId);
     }
 
+    /**
+     * Obtains or parses GrpcExtInfo for metadata.
+     *
+     * @param metaData metaData
+     * @return parsed or cached GrpcExtInfo
+     */
+    public GrpcExtInfo obtainGrpcExtInfo(final MetaData metaData) {
+        if (Objects.isNull(metaData) || 
StringUtils.isBlank(metaData.getRpcExt())) {
+            return DEFAULT_EXT_INFO;
+        }
+        final String key = buildCacheKey(metaData);
+        final String rpcExt = metaData.getRpcExt();
+        CachedGrpcExtInfo cached = grpcExtCache.getIfPresent(key);
+        if (Objects.nonNull(cached) && Objects.equals(cached.rpcExt, rpcExt)) {
+            return cached.extInfo;
+        }
+        GrpcExtInfo parsed = parseRpcExt(rpcExt);
+        grpcExtCache.put(key, new CachedGrpcExtInfo(rpcExt, parsed));
+        return parsed;
+    }
+
+    /**
+     * Cache GrpcExtInfo for metadata on metadata update.
+     *
+     * @param metaData metaData
+     */
+    public void cacheGrpcExtInfo(final MetaData metaData) {
+        if (Objects.isNull(metaData)) {
+            return;
+        }
+        final String key = buildCacheKey(metaData);
+        final String rpcExt = metaData.getRpcExt();
+        if (StringUtils.isBlank(rpcExt)) {
+            grpcExtCache.put(key, new CachedGrpcExtInfo(rpcExt, 
DEFAULT_EXT_INFO));
+            return;
+        }
+        try {
+            GrpcExtInfo extInfo = parseRpcExt(rpcExt);
+            grpcExtCache.put(key, new CachedGrpcExtInfo(rpcExt, extInfo));
+        } catch (Exception e) {
+            LOG.warn("Failed to parse gRPC rpcExt for metadata: {}", key, e);
+        }
+    }
+
+    /**
+     * Invalidate GrpcExtInfo for metadata on metadata removal.
+     *
+     * @param metaData metaData
+     */
+    public void invalidateGrpcExtInfo(final MetaData metaData) {
+        if (Objects.isNull(metaData)) {
+            return;
+        }
+        if (StringUtils.isNotBlank(metaData.getPath())) {
+            grpcExtCache.invalidate(metaData.getPath());
+        }
+        if (StringUtils.isNotBlank(metaData.getId())) {
+            grpcExtCache.invalidate(metaData.getId());
+        }
+        if (StringUtils.isNotBlank(metaData.getServiceName()) && 
StringUtils.isNotBlank(metaData.getMethodName())) {
+            grpcExtCache.invalidate(metaData.getServiceName() + "." + 
metaData.getMethodName());
+        }
+    }
+
+    /**
+     * Clean GrpcExtInfo cache.
+     */
+    public void cleanGrpcExtInfo() {
+        grpcExtCache.invalidateAll();
+    }
+
+    private GrpcExtInfo parseRpcExt(final String rpcExt) {
+        if (StringUtils.isBlank(rpcExt)) {
+            return DEFAULT_EXT_INFO;
+        }
+        GrpcExtInfo extInfo = GsonUtils.getGson().fromJson(rpcExt, 
GrpcExtInfo.class);
+        if (Objects.isNull(extInfo)) {
+            return DEFAULT_EXT_INFO;
+        }
+        if (Objects.isNull(extInfo.getTimeout())) {
+            extInfo.setTimeout(5000);
+        }
+        if (Objects.isNull(extInfo.getMethodType())) {
+            extInfo.setMethodType(MethodDescriptor.MethodType.UNARY);
+        }
+        return extInfo;
+    }
+
+    private String buildCacheKey(final MetaData metaData) {
+        if (StringUtils.isNotBlank(metaData.getPath())) {
+            return metaData.getPath();
+        }
+        if (StringUtils.isNotBlank(metaData.getId())) {
+            return metaData.getId();
+        }
+        return metaData.getServiceName() + "." + metaData.getMethodName();
+    }
+
+    static final class CachedGrpcExtInfo {
+
+        private final String rpcExt;
+
+        private final GrpcExtInfo extInfo;
+
+        CachedGrpcExtInfo(final String rpcExt, final GrpcExtInfo extInfo) {
+            this.rpcExt = rpcExt;
+            this.extInfo = extInfo;
+        }
+    }
+
 
     /**
      * The type Application config cache instance.
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/cache/GrpcExtInfo.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/cache/GrpcExtInfo.java
new file mode 100644
index 0000000000..26be8e3fcc
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/cache/GrpcExtInfo.java
@@ -0,0 +1,102 @@
+/*
+ * 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.grpc.cache;
+
+import io.grpc.MethodDescriptor;
+
+import java.util.Objects;
+
+/**
+ * The GrpcExtInfo.
+ */
+public class GrpcExtInfo {
+
+    private Integer timeout = 5000;
+
+    private MethodDescriptor.MethodType methodType = 
MethodDescriptor.MethodType.UNARY;
+
+    /**
+     * Instantiates a new Grpc ext info.
+     */
+    public GrpcExtInfo() {
+    }
+
+    /**
+     * Instantiates a new Grpc ext info.
+     *
+     * @param timeout timeout
+     * @param methodType methodType
+     */
+    public GrpcExtInfo(final Integer timeout, final 
MethodDescriptor.MethodType methodType) {
+        this.timeout = timeout;
+        this.methodType = methodType;
+    }
+
+    /**
+     * Gets timeout.
+     *
+     * @return timeout
+     */
+    public Integer getTimeout() {
+        return timeout;
+    }
+
+    /**
+     * Sets timeout.
+     *
+     * @param timeout timeout
+     */
+    public void setTimeout(final Integer timeout) {
+        this.timeout = timeout;
+    }
+
+    /**
+     * Gets methodType.
+     *
+     * @return methodType
+     */
+    public MethodDescriptor.MethodType getMethodType() {
+        return methodType;
+    }
+
+    /**
+     * Sets methodType.
+     *
+     * @param methodType methodType
+     */
+    public void setMethodType(final MethodDescriptor.MethodType methodType) {
+        this.methodType = methodType;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (Objects.isNull(o) || getClass() != o.getClass()) {
+            return false;
+        }
+        GrpcExtInfo that = (GrpcExtInfo) o;
+        return Objects.equals(timeout, that.timeout) && methodType == 
that.methodType;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(timeout, methodType);
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcMetaDataHandler.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcMetaDataHandler.java
new file mode 100644
index 0000000000..54a2cc40f7
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcMetaDataHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.grpc.handler;
+
+import org.apache.shenyu.common.dto.MetaData;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.apache.shenyu.plugin.base.handler.MetaDataHandler;
+import org.apache.shenyu.plugin.grpc.cache.ApplicationConfigCache;
+
+/**
+ * The type grpc meta data handler.
+ */
+public class GrpcMetaDataHandler implements MetaDataHandler {
+
+    @Override
+    public void handle(final MetaData metaData) {
+        ApplicationConfigCache.getInstance().cacheGrpcExtInfo(metaData);
+    }
+
+    @Override
+    public void remove(final MetaData metaData) {
+        ApplicationConfigCache.getInstance().invalidateGrpcExtInfo(metaData);
+    }
+
+    @Override
+    public void refresh() {
+        ApplicationConfigCache.getInstance().cleanGrpcExtInfo();
+    }
+
+    @Override
+    public String rpcType() {
+        return RpcTypeEnum.GRPC.getName();
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/GrpcPluginTest.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/GrpcPluginTest.java
index 6ce28918b1..2a4048a3d7 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/GrpcPluginTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/GrpcPluginTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.shenyu.plugin.grpc;
 
+import io.grpc.CallOptions;
 import io.grpc.MethodDescriptor;
 import org.apache.shenyu.common.constant.Constants;
 import org.apache.shenyu.common.dto.MetaData;
@@ -29,7 +30,9 @@ import org.apache.shenyu.plugin.api.context.ShenyuContext;
 import org.apache.shenyu.plugin.api.result.DefaultShenyuResult;
 import org.apache.shenyu.plugin.api.result.ShenyuResult;
 import org.apache.shenyu.plugin.api.utils.SpringBeanUtils;
+import org.apache.shenyu.plugin.grpc.cache.ApplicationConfigCache;
 import org.apache.shenyu.plugin.grpc.cache.GrpcClientCache;
+import org.apache.shenyu.plugin.grpc.cache.GrpcExtInfo;
 import org.apache.shenyu.plugin.grpc.client.ShenyuGrpcClient;
 import org.apache.shenyu.plugin.grpc.context.GrpcConstants;
 import org.apache.shenyu.plugin.grpc.proto.ShenyuGrpcResponse;
@@ -58,6 +61,8 @@ import java.util.concurrent.CompletableFuture;
 
 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.assertSame;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -150,6 +155,9 @@ public class GrpcPluginTest {
                 .thenAnswer(invocation -> {
                     assertEquals(expectedRemoteAddress, 
GrpcConstants.GRPC_REMOTE_ADDRESS.get());
                     assertEquals(expectedMethodType, 
invocation.getArgument(3));
+                    CallOptions callOptions = invocation.getArgument(1);
+                    assertNotNull(callOptions);
+                    assertNotNull(callOptions.getDeadline());
                     return CompletableFuture.completedFuture(response);
                 });
         clientCacheMap.put("grpcId", mockClient);
@@ -159,6 +167,22 @@ public class GrpcPluginTest {
         StepVerifier.create(grpcPlugin.doExecute(exchange, chain, selector, 
data)).expectSubscription().verifyComplete();
     }
 
+    @Test
+    public void testDoExecuteReusesParsedRpcExt()
+            throws ClassNotFoundException, NoSuchFieldException, 
IllegalAccessException {
+        ServerWebExchange exchange1 = getServerWebExchange();
+        MetaData metaData = getMetaData();
+        executeRequest(exchange1, "", metaData, 
MethodDescriptor.MethodType.SERVER_STREAMING);
+
+        GrpcExtInfo cached = 
ApplicationConfigCache.getInstance().obtainGrpcExtInfo(metaData);
+
+        ServerWebExchange exchange2 = getServerWebExchange();
+        executeRequest(exchange2, "", metaData, 
MethodDescriptor.MethodType.SERVER_STREAMING);
+
+        GrpcExtInfo second = 
ApplicationConfigCache.getInstance().obtainGrpcExtInfo(metaData);
+        assertSame(cached, second);
+    }
+
     @Test
     public void testDoExecuteMetaDataError() {
         ServerWebExchange exchange = getServerWebExchange();
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/cache/ApplicationConfigCacheTest.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/cache/ApplicationConfigCacheTest.java
index 39be026b7c..a0b8ed2450 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/cache/ApplicationConfigCacheTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/cache/ApplicationConfigCacheTest.java
@@ -75,4 +75,81 @@ public class ApplicationConfigCacheTest {
     public void testWatch() {
         this.applicationConfigCache.watch(selector.getName(), 
Assertions::assertNotNull);
     }
+
+    @Test
+    public void testObtainGrpcExtInfo() {
+        org.apache.shenyu.common.dto.MetaData metaData = 
org.apache.shenyu.common.dto.MetaData.builder()
+                .path("/grpc/echo")
+                .serviceName("echoService")
+                .methodName("echo")
+                .rpcExt("{\"methodType\":\"UNARY\",\"timeout\":2500}")
+                .build();
+
+        GrpcExtInfo info1 = 
this.applicationConfigCache.obtainGrpcExtInfo(metaData);
+        org.junit.jupiter.api.Assertions.assertEquals(2500, 
info1.getTimeout());
+        
org.junit.jupiter.api.Assertions.assertEquals(io.grpc.MethodDescriptor.MethodType.UNARY,
 info1.getMethodType());
+
+        GrpcExtInfo info2 = 
this.applicationConfigCache.obtainGrpcExtInfo(metaData);
+        org.junit.jupiter.api.Assertions.assertSame(info1, info2);
+    }
+
+    @Test
+    public void testObtainGrpcExtInfoDefaults() {
+        
org.junit.jupiter.api.Assertions.assertNotNull(this.applicationConfigCache.obtainGrpcExtInfo(null));
+
+        org.apache.shenyu.common.dto.MetaData blankMeta = 
org.apache.shenyu.common.dto.MetaData.builder()
+                .path("/grpc/blank")
+                .rpcExt("")
+                .build();
+        GrpcExtInfo blankInfo = 
this.applicationConfigCache.obtainGrpcExtInfo(blankMeta);
+        org.junit.jupiter.api.Assertions.assertEquals(5000, 
blankInfo.getTimeout());
+        
org.junit.jupiter.api.Assertions.assertEquals(io.grpc.MethodDescriptor.MethodType.UNARY,
 blankInfo.getMethodType());
+
+        org.apache.shenyu.common.dto.MetaData emptyJsonMeta = 
org.apache.shenyu.common.dto.MetaData.builder()
+                .path("/grpc/emptyJson")
+                .rpcExt("{}")
+                .build();
+        GrpcExtInfo emptyJsonInfo = 
this.applicationConfigCache.obtainGrpcExtInfo(emptyJsonMeta);
+        org.junit.jupiter.api.Assertions.assertEquals(5000, 
emptyJsonInfo.getTimeout());
+        
org.junit.jupiter.api.Assertions.assertEquals(io.grpc.MethodDescriptor.MethodType.UNARY,
 emptyJsonInfo.getMethodType());
+    }
+
+    @Test
+    public void testObtainGrpcExtInfoUpdate() {
+        org.apache.shenyu.common.dto.MetaData metaData = 
org.apache.shenyu.common.dto.MetaData.builder()
+                .path("/grpc/update")
+                .rpcExt("{\"methodType\":\"UNARY\",\"timeout\":1000}")
+                .build();
+
+        GrpcExtInfo initial = 
this.applicationConfigCache.obtainGrpcExtInfo(metaData);
+        org.junit.jupiter.api.Assertions.assertEquals(1000, 
initial.getTimeout());
+
+        
metaData.setRpcExt("{\"methodType\":\"SERVER_STREAMING\",\"timeout\":4000}");
+        GrpcExtInfo updated = 
this.applicationConfigCache.obtainGrpcExtInfo(metaData);
+        org.junit.jupiter.api.Assertions.assertEquals(4000, 
updated.getTimeout());
+        
org.junit.jupiter.api.Assertions.assertEquals(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING,
 updated.getMethodType());
+        org.junit.jupiter.api.Assertions.assertNotSame(initial, updated);
+    }
+
+    @Test
+    public void testInvalidateAndCleanGrpcExtInfo() {
+        org.apache.shenyu.common.dto.MetaData metaData = 
org.apache.shenyu.common.dto.MetaData.builder()
+                .id("meta-1")
+                .path("/grpc/invalidate")
+                .serviceName("service1")
+                .methodName("method1")
+                .rpcExt("{\"methodType\":\"UNARY\",\"timeout\":6000}")
+                .build();
+
+        GrpcExtInfo info1 = 
this.applicationConfigCache.obtainGrpcExtInfo(metaData);
+        org.junit.jupiter.api.Assertions.assertSame(info1, 
this.applicationConfigCache.obtainGrpcExtInfo(metaData));
+
+        this.applicationConfigCache.invalidateGrpcExtInfo(metaData);
+        GrpcExtInfo info2 = 
this.applicationConfigCache.obtainGrpcExtInfo(metaData);
+        org.junit.jupiter.api.Assertions.assertNotSame(info1, info2);
+
+        this.applicationConfigCache.cleanGrpcExtInfo();
+        GrpcExtInfo info3 = 
this.applicationConfigCache.obtainGrpcExtInfo(metaData);
+        org.junit.jupiter.api.Assertions.assertNotSame(info2, info3);
+    }
 }
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/handler/GrpcMetaDataHandlerTest.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/handler/GrpcMetaDataHandlerTest.java
new file mode 100644
index 0000000000..019977d493
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/handler/GrpcMetaDataHandlerTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.grpc.handler;
+
+import io.grpc.MethodDescriptor;
+import org.apache.shenyu.common.dto.MetaData;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.apache.shenyu.plugin.grpc.cache.ApplicationConfigCache;
+import org.apache.shenyu.plugin.grpc.cache.GrpcExtInfo;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * The Test Case For {@link GrpcMetaDataHandler}.
+ */
+public class GrpcMetaDataHandlerTest {
+
+    private GrpcMetaDataHandler grpcMetaDataHandler;
+
+    @BeforeEach
+    public void setUp() {
+        this.grpcMetaDataHandler = new GrpcMetaDataHandler();
+        ApplicationConfigCache.getInstance().cleanGrpcExtInfo();
+    }
+
+    @Test
+    public void testRpcType() {
+        assertEquals(RpcTypeEnum.GRPC.getName(), 
grpcMetaDataHandler.rpcType());
+    }
+
+    @Test
+    public void testHandleAndRemoveAndRefresh() {
+        MetaData metaData = MetaData.builder()
+                .id("1")
+                .path("/grpc/test")
+                .serviceName("testService")
+                .methodName("testMethod")
+                .rpcExt("{\"methodType\":\"UNARY\",\"timeout\":3000}")
+                .build();
+
+        grpcMetaDataHandler.handle(metaData);
+        GrpcExtInfo info1 = 
ApplicationConfigCache.getInstance().obtainGrpcExtInfo(metaData);
+        assertEquals(3000, info1.getTimeout());
+        assertEquals(MethodDescriptor.MethodType.UNARY, info1.getMethodType());
+
+        GrpcExtInfo info2 = 
ApplicationConfigCache.getInstance().obtainGrpcExtInfo(metaData);
+        assertSame(info1, info2);
+
+        grpcMetaDataHandler.remove(metaData);
+        // After removal, next obtain re-parses and returns new instance
+        GrpcExtInfo info3 = 
ApplicationConfigCache.getInstance().obtainGrpcExtInfo(metaData);
+        assertEquals(3000, info3.getTimeout());
+
+        grpcMetaDataHandler.refresh();
+        GrpcExtInfo info4 = 
ApplicationConfigCache.getInstance().obtainGrpcExtInfo(metaData);
+        assertEquals(3000, info4.getTimeout());
+    }
+}
diff --git 
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-grpc/src/main/java/org/apache/shenyu/springboot/starter/plugin/grpc/GrpcPluginConfiguration.java
 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-grpc/src/main/java/org/apache/shenyu/springboot/starter/plugin/grpc/GrpcPluginConfiguration.java
index 0b4bb9da76..2be3f327c2 100644
--- 
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-grpc/src/main/java/org/apache/shenyu/springboot/starter/plugin/grpc/GrpcPluginConfiguration.java
+++ 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-grpc/src/main/java/org/apache/shenyu/springboot/starter/plugin/grpc/GrpcPluginConfiguration.java
@@ -20,10 +20,12 @@ package org.apache.shenyu.springboot.starter.plugin.grpc;
 import org.apache.shenyu.plugin.api.ShenyuPlugin;
 import org.apache.shenyu.plugin.api.context.ShenyuContextDecorator;
 import org.apache.shenyu.plugin.base.handler.DiscoveryUpstreamDataHandler;
+import org.apache.shenyu.plugin.base.handler.MetaDataHandler;
 import org.apache.shenyu.plugin.base.handler.PluginDataHandler;
 import org.apache.shenyu.plugin.grpc.GrpcPlugin;
 import org.apache.shenyu.plugin.grpc.context.GrpcShenyuContextDecorator;
 import org.apache.shenyu.plugin.grpc.handler.GrpcDiscoveryUpstreamDataHandler;
+import org.apache.shenyu.plugin.grpc.handler.GrpcMetaDataHandler;
 import org.apache.shenyu.plugin.grpc.handler.GrpcPluginDataHandler;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
@@ -76,4 +78,14 @@ public class GrpcPluginConfiguration {
     public ShenyuContextDecorator grpcShenyuContextDecorator() {
         return new GrpcShenyuContextDecorator();
     }
+
+    /**
+     * Grpc meta data handler.
+     *
+     * @return the meta data handler
+     */
+    @Bean
+    public MetaDataHandler grpcMetaDataHandler() {
+        return new GrpcMetaDataHandler();
+    }
 }
diff --git 
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-grpc/src/test/java/org/apache/shenyu/springboot/starter/plugin/grpc/GrpcPluginConfigurationTest.java
 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-grpc/src/test/java/org/apache/shenyu/springboot/starter/plugin/grpc/GrpcPluginConfigurationTest.java
index 2927a188cd..a9811e03b6 100644
--- 
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-grpc/src/test/java/org/apache/shenyu/springboot/starter/plugin/grpc/GrpcPluginConfigurationTest.java
+++ 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-grpc/src/test/java/org/apache/shenyu/springboot/starter/plugin/grpc/GrpcPluginConfigurationTest.java
@@ -19,6 +19,7 @@ package org.apache.shenyu.springboot.starter.plugin.grpc;
 
 import org.apache.shenyu.plugin.api.ShenyuPlugin;
 import org.apache.shenyu.plugin.api.context.ShenyuContextDecorator;
+import org.apache.shenyu.plugin.base.handler.MetaDataHandler;
 import org.apache.shenyu.plugin.base.handler.PluginDataHandler;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -72,4 +73,13 @@ public class GrpcPluginConfigurationTest {
             }
         );
     }
+
+    @Test
+    public void testGrpcMetaDataHandler() {
+        applicationContextRunner.run(context -> {
+                MetaDataHandler handler = 
context.getBean("grpcMetaDataHandler", MetaDataHandler.class);
+                assertNotNull(handler);
+            }
+        );
+    }
 }

Reply via email to