You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by ox...@apache.org on 2024/11/03 05:02:33 UTC

(dubbo) branch 3.3 updated: Support parsing custom REST paths from proto files and generating @Ma… (#14796)

This is an automated email from the ASF dual-hosted git repository.

oxsean pushed a commit to branch 3.3
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/3.3 by this push:
     new 9741de32e5 Support parsing custom REST paths from proto files and generating @Ma… (#14796)
9741de32e5 is described below

commit 9741de32e54b88d732c865356749661dcb7c4328
Author: heliang666s <14...@users.noreply.github.com>
AuthorDate: Sun Nov 3 13:02:27 2024 +0800

    Support parsing custom REST paths from proto files and generating @Ma… (#14796)
---
 dubbo-plugin/dubbo-compiler/pom.xml                |  1 -
 .../org/apache/dubbo/gen/AbstractGenerator.java    | 91 +++++++++++++++++++++-
 .../resources/Dubbo3TripleInterfaceStub.mustache   | 54 ++++++-------
 .../src/main/resources/Dubbo3TripleStub.mustache   | 82 ++++---------------
 .../dubbo/rpc/stub/annotations/GRequest.java       | 30 +++++++
 .../support/basic/GRequestArgumentResolver.java    | 85 ++++++++++++++++++++
 ...rpc.protocol.tri.rest.argument.ArgumentResolver |  1 +
 7 files changed, 248 insertions(+), 96 deletions(-)

diff --git a/dubbo-plugin/dubbo-compiler/pom.xml b/dubbo-plugin/dubbo-compiler/pom.xml
index adaeb057de..48c800e7fe 100644
--- a/dubbo-plugin/dubbo-compiler/pom.xml
+++ b/dubbo-plugin/dubbo-compiler/pom.xml
@@ -100,5 +100,4 @@
       </plugin>
     </plugins>
   </build>
-
 </project>
diff --git a/dubbo-plugin/dubbo-compiler/src/main/java/org/apache/dubbo/gen/AbstractGenerator.java b/dubbo-plugin/dubbo-compiler/src/main/java/org/apache/dubbo/gen/AbstractGenerator.java
index c434ec47fc..7ea75cf539 100644
--- a/dubbo-plugin/dubbo-compiler/src/main/java/org/apache/dubbo/gen/AbstractGenerator.java
+++ b/dubbo-plugin/dubbo-compiler/src/main/java/org/apache/dubbo/gen/AbstractGenerator.java
@@ -33,6 +33,9 @@ import java.util.stream.Collectors;
 import com.github.mustachejava.DefaultMustacheFactory;
 import com.github.mustachejava.Mustache;
 import com.github.mustachejava.MustacheFactory;
+import com.google.api.AnnotationsProto;
+import com.google.api.HttpRule;
+import com.google.api.HttpRule.PatternCase;
 import com.google.common.base.Charsets;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
@@ -42,6 +45,7 @@ import com.google.protobuf.DescriptorProtos.FileOptions;
 import com.google.protobuf.DescriptorProtos.MethodDescriptorProto;
 import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto;
 import com.google.protobuf.DescriptorProtos.SourceCodeInfo.Location;
+import com.google.protobuf.ExtensionRegistry;
 import com.google.protobuf.compiler.PluginProtos;
 
 public abstract class AbstractGenerator {
@@ -67,24 +71,40 @@ public abstract class AbstractGenerator {
     }
 
     private String getServiceJavaDocPrefix() {
-        return "    ";
+        return "";
     }
 
     private String getMethodJavaDocPrefix() {
-        return "        ";
+        return "    ";
     }
 
     public List<PluginProtos.CodeGeneratorResponse.File> generateFiles(PluginProtos.CodeGeneratorRequest request) {
-        final ProtoTypeMap typeMap = ProtoTypeMap.of(request.getProtoFileList());
+        // 1. build ExtensionRegistry and registry
+        ExtensionRegistry registry = ExtensionRegistry.newInstance();
+        AnnotationsProto.registerAllExtensions(registry);
 
+        // 2. compile proto file
         List<FileDescriptorProto> protosToGenerate = request.getProtoFileList().stream()
                 .filter(protoFile -> request.getFileToGenerateList().contains(protoFile.getName()))
+                .map(protoFile -> parseWithExtensions(protoFile, registry))
                 .collect(Collectors.toList());
 
+        // 3. use compiled proto file build ProtoTypeMap
+        final ProtoTypeMap typeMap = ProtoTypeMap.of(protosToGenerate);
+
+        // 4. find and generate serviceContext
         List<ServiceContext> services = findServices(protosToGenerate, typeMap);
         return generateFiles(services);
     }
 
+    private FileDescriptorProto parseWithExtensions(FileDescriptorProto protoFile, ExtensionRegistry registry) {
+        try {
+            return FileDescriptorProto.parseFrom(protoFile.toByteString(), registry);
+        } catch (Exception e) {
+            return protoFile;
+        }
+    }
+
     private List<ServiceContext> findServices(List<FileDescriptorProto> protos, ProtoTypeMap typeMap) {
         List<ServiceContext> contexts = new ArrayList<>();
 
@@ -171,6 +191,40 @@ public abstract class AbstractGenerator {
         methodContext.isManyOutput = methodProto.getServerStreaming();
         methodContext.methodNumber = methodNumber;
 
+        // compile google.api.http option
+        HttpRule httpRule = parseHttpRule(methodProto);
+        if (httpRule != null) {
+            PatternCase patternCase = httpRule.getPatternCase();
+            String path;
+            switch (patternCase) {
+                case GET:
+                    path = httpRule.getGet();
+                    break;
+                case PUT:
+                    path = httpRule.getPut();
+                    break;
+                case POST:
+                    path = httpRule.getPost();
+                    break;
+                case DELETE:
+                    path = httpRule.getDelete();
+                    break;
+                case PATCH:
+                    path = httpRule.getPatch();
+                    break;
+                default:
+                    path = "";
+                    break;
+            }
+            if (!path.isEmpty()) {
+                methodContext.httpMethod = patternCase.name();
+                methodContext.path = path;
+                methodContext.body = httpRule.getBody();
+                methodContext.hasMapping = true;
+                methodContext.needRequestAnnotation = !methodContext.body.isEmpty() || path.contains("{");
+            }
+        }
+
         Location methodLocation = locations.stream()
                 .filter(location -> location.getPathCount() == METHOD_NUMBER_OF_PATHS
                         && location.getPath(METHOD_NUMBER_OF_PATHS - 1) == methodNumber)
@@ -197,6 +251,17 @@ public abstract class AbstractGenerator {
         return methodContext;
     }
 
+    private HttpRule parseHttpRule(MethodDescriptorProto methodProto) {
+        HttpRule rule = null;
+        // check methodProto have options
+        if (methodProto.hasOptions()) {
+            if (methodProto.getOptions().hasExtension(AnnotationsProto.http)) {
+                rule = methodProto.getOptions().getExtension(AnnotationsProto.http);
+            }
+        }
+        return rule;
+    }
+
     private String lowerCaseFirst(String s) {
         return Character.toLowerCase(s.charAt(0)) + s.substring(1);
     }
@@ -360,6 +425,26 @@ public abstract class AbstractGenerator {
         public String grpcCallsMethodName;
         public int methodNumber;
         public String javaDoc;
+        /**
+         * The HTTP request method
+         */
+        public String httpMethod;
+        /**
+         * The HTTP request path
+         */
+        public String path;
+        /**
+         * The message field that the HTTP request body mapping to
+         */
+        public String body;
+        /**
+         * Whether the method has HTTP mapping
+         */
+        public boolean hasMapping;
+        /**
+         * Whether the request body parameter need @GRequest annotation
+         */
+        public boolean needRequestAnnotation;
 
         // This method mimics the upper-casing method ogf gRPC to ensure compatibility
         // See https://github.com/grpc/grpc-java/blob/v1.8.0/compiler/src/java_plugin/cpp/java_generator.cpp#L58
diff --git a/dubbo-plugin/dubbo-compiler/src/main/resources/Dubbo3TripleInterfaceStub.mustache b/dubbo-plugin/dubbo-compiler/src/main/resources/Dubbo3TripleInterfaceStub.mustache
index d31fed7efb..559e2b9e5c 100644
--- a/dubbo-plugin/dubbo-compiler/src/main/resources/Dubbo3TripleInterfaceStub.mustache
+++ b/dubbo-plugin/dubbo-compiler/src/main/resources/Dubbo3TripleInterfaceStub.mustache
@@ -1,25 +1,28 @@
 /*
-* 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.
-*/
+ * 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.
+ */
 
 {{#packageName}}
 package {{packageName}};
 {{/packageName}}
 
 import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.remoting.http12.HttpMethods;
+import org.apache.dubbo.remoting.http12.rest.Mapping;
+import org.apache.dubbo.rpc.stub.annotations.GRequest;
 import com.google.protobuf.Message;
 
 import java.util.HashMap;
@@ -28,7 +31,6 @@ import java.util.function.BiConsumer;
 import java.util.concurrent.CompletableFuture;
 
 public interface {{interfaceClassName}} extends org.apache.dubbo.rpc.model.DubboStub {
-
     String JAVA_SERVICE_NAME = "{{packageName}}.{{serviceName}}";
 {{#commonPackageName}}
     String SERVICE_NAME = "{{commonPackageName}}.{{serviceName}}";
@@ -40,34 +42,32 @@ public interface {{interfaceClassName}} extends org.apache.dubbo.rpc.model.Dubbo
     {{#javaDoc}}
     {{{javaDoc}}}
     {{/javaDoc}}
-    {{outputType}} {{methodName}}({{inputType}} request);
-
-    CompletableFuture<{{outputType}}> {{methodName}}Async({{inputType}} request);
-
-
+    {{#hasMapping}}
+    @Mapping(method = HttpMethods.{{httpMethod}}, path = "{{path}}")
+    {{/hasMapping}}
+    {{outputType}} {{methodName}}({{#needRequestAnnotation}}@GRequest{{#body}}("{{body}}"){{/body}} {{/needRequestAnnotation}}{{inputType}} request);
 
+    CompletableFuture<{{outputType}}> {{methodName}}Async({{#needRequestAnnotation}}@GRequest {{/needRequestAnnotation}}{{inputType}} request);
 {{/unaryMethods}}
-
 {{#serverStreamingMethods}}
     {{#javaDoc}}
     {{{javaDoc}}}
     {{/javaDoc}}
-    void {{methodName}}({{inputType}} request, StreamObserver<{{outputType}}> responseObserver);
+    {{#hasMapping}}
+    @Mapping(method = HttpMethods.{{httpMethod}}, path = "{{path}}")
+    {{/hasMapping}}
+    void {{methodName}}({{#needRequestAnnotation}}@GRequest{{#body}}("{{body}}"){{/body}} {{/needRequestAnnotation}}{{inputType}} request, StreamObserver<{{outputType}}> responseObserver);
 {{/serverStreamingMethods}}
-
 {{#biStreamingWithoutClientStreamMethods}}
     {{#javaDoc}}
     {{{javaDoc}}}
     {{/javaDoc}}
     StreamObserver<{{inputType}}> {{methodName}}(StreamObserver<{{outputType}}> responseObserver);
 {{/biStreamingWithoutClientStreamMethods}}
-
-
 {{#clientStreamingMethods}}
     {{#javaDoc}}
     {{{javaDoc}}}
     {{/javaDoc}}
     StreamObserver<{{inputType}}> {{methodName}}(StreamObserver<{{outputType}}> responseObserver);
 {{/clientStreamingMethods}}
-
 }
diff --git a/dubbo-plugin/dubbo-compiler/src/main/resources/Dubbo3TripleStub.mustache b/dubbo-plugin/dubbo-compiler/src/main/resources/Dubbo3TripleStub.mustache
index 6f19d17fb1..94572b36e4 100644
--- a/dubbo-plugin/dubbo-compiler/src/main/resources/Dubbo3TripleStub.mustache
+++ b/dubbo-plugin/dubbo-compiler/src/main/resources/Dubbo3TripleStub.mustache
@@ -1,19 +1,19 @@
 /*
-* 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.
-*/
+ * 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.
+ */
 
 {{#packageName}}
 package {{packageName}};
@@ -47,7 +47,6 @@ import java.util.function.BiConsumer;
 import java.util.concurrent.CompletableFuture;
 
 public final class {{className}} {
-
     public static final String SERVICE_NAME = {{interfaceClassName}}.SERVICE_NAME;
 
     private static final StubServiceDescriptor serviceDescriptor = new StubServiceDescriptor(SERVICE_NAME,{{interfaceClassName}}.class);
@@ -64,7 +63,6 @@ public final class {{className}} {
     public static {{interfaceClassName}} newStub(Invoker<?> invoker) {
         return new {{interfaceClassName}}Stub((Invoker<{{interfaceClassName}}>)invoker);
     }
-
 {{#unaryMethods}}
     {{#javaDoc}}
     {{{javaDoc}}}
@@ -84,7 +82,6 @@ public final class {{className}} {
     obj -> ((Message) obj).toByteArray(), obj -> ((Message) obj).toByteArray(), {{inputType}}::parseFrom,
     {{outputType}}::parseFrom);
 {{/unaryMethods}}
-
 {{#serverStreamingMethods}}
     {{#javaDoc}}
     {{{javaDoc}}}
@@ -94,7 +91,6 @@ public final class {{className}} {
     obj -> ((Message) obj).toByteArray(), obj -> ((Message) obj).toByteArray(), {{inputType}}::parseFrom,
     {{outputType}}::parseFrom);
 {{/serverStreamingMethods}}
-
 {{#clientStreamingMethods}}
     {{#javaDoc}}
     {{{javaDoc}}}
@@ -104,7 +100,6 @@ public final class {{className}} {
     obj -> ((Message) obj).toByteArray(), obj -> ((Message) obj).toByteArray(), {{inputType}}::parseFrom,
     {{outputType}}::parseFrom);
 {{/clientStreamingMethods}}
-
 {{#biStreamingWithoutClientStreamMethods}}
     {{#javaDoc}}
     {{{javaDoc}}}
@@ -142,52 +137,32 @@ public final class {{className}} {
         public void $destroy() {
               invoker.destroy();
          }
-
     {{#unaryMethods}}
-        {{#javaDoc}}
-        {{{javaDoc}}}
-        {{/javaDoc}}
         @Override
         public {{outputType}} {{methodName}}({{inputType}} request){
             return StubInvocationUtil.unaryCall(invoker, {{methodName}}Method, request);
         }
-
         public CompletableFuture<{{outputType}}> {{methodName}}Async({{inputType}} request){
             return StubInvocationUtil.unaryCall(invoker, {{methodName}}AsyncMethod, request);
         }
 
-        {{#javaDoc}}
-        {{{javaDoc}}}
-        {{/javaDoc}}
         public void {{methodName}}({{inputType}} request, StreamObserver<{{outputType}}> responseObserver){
             StubInvocationUtil.unaryCall(invoker, {{methodName}}Method , request, responseObserver);
         }
     {{/unaryMethods}}
-
     {{#serverStreamingMethods}}
-        {{#javaDoc}}
-        {{{javaDoc}}}
-        {{/javaDoc}}
         @Override
         public void {{methodName}}({{inputType}} request, StreamObserver<{{outputType}}> responseObserver){
             StubInvocationUtil.serverStreamCall(invoker, {{methodName}}Method , request, responseObserver);
         }
     {{/serverStreamingMethods}}
-
     {{#biStreamingWithoutClientStreamMethods}}
-        {{#javaDoc}}
-        {{{javaDoc}}}
-        {{/javaDoc}}
         @Override
         public StreamObserver<{{inputType}}> {{methodName}}(StreamObserver<{{outputType}}> responseObserver){
             return StubInvocationUtil.biOrClientStreamCall(invoker, {{methodName}}Method , responseObserver);
         }
     {{/biStreamingWithoutClientStreamMethods}}
-
     {{#clientStreamingMethods}}
-        {{#javaDoc}}
-        {{{javaDoc}}}
-        {{/javaDoc}}
         @Override
         public StreamObserver<{{inputType}}> {{methodName}}(StreamObserver<{{outputType}}> responseObserver){
         return StubInvocationUtil.biOrClientStreamCall(invoker, {{methodName}}Method , responseObserver);
@@ -196,7 +171,6 @@ public final class {{className}} {
     }
 
     public static abstract class {{interfaceClassName}}ImplBase implements {{interfaceClassName}}, ServerService<{{interfaceClassName}}> {
-
         private <T, R> BiConsumer<T, StreamObserver<R>> syncToAsync(java.util.function.Function<T, R> syncFun) {
             return new BiConsumer<T, StreamObserver<R>>() {
                 @Override
@@ -211,7 +185,6 @@ public final class {{className}} {
                 }
             };
         }
-
         {{#unaryMethods}}
         @Override
         public CompletableFuture<{{outputType}}> {{methodName}}Async({{inputType}} request){
@@ -219,10 +192,8 @@ public final class {{className}} {
         }
         {{/unaryMethods}}
 
-        /**
-        * This server stream type unary method is <b>only</b> used for generated stub to support async unary method.
-        * It will not be called if you are NOT using Dubbo3 generated triple stub and <b>DO NOT</b> implement this method.
-        */
+        // This server stream type unary method is <b>only</b> used for generated stub to support async unary method.
+        // It will not be called if you are NOT using Dubbo3 generated triple stub and <b>DO NOT</b> implement this method.
         {{#unaryMethods}}
         public void {{methodName}}({{inputType}} request, StreamObserver<{{outputType}}> responseObserver){
             {{methodName}}Async(request).whenComplete((r, t) -> {
@@ -242,68 +213,50 @@ public final class {{className}} {
             .getExtensionLoader(PathResolver.class)
             .getDefaultExtension();
             Map<String,StubMethodHandler<?, ?>> handlers = new HashMap<>();
-
         {{#methods}}
             pathResolver.addNativeStub( "/" + SERVICE_NAME + "/{{originMethodName}}");
             pathResolver.addNativeStub( "/" + SERVICE_NAME + "/{{originMethodName}}Async");
             // for compatibility
             pathResolver.addNativeStub( "/" + JAVA_SERVICE_NAME + "/{{originMethodName}}");
             pathResolver.addNativeStub( "/" + JAVA_SERVICE_NAME + "/{{originMethodName}}Async");
-
         {{/methods}}
-
         {{#unaryMethods}}
             BiConsumer<{{inputType}}, StreamObserver<{{outputType}}>> {{methodName}}Func = this::{{methodName}};
             handlers.put({{methodName}}Method.getMethodName(), new UnaryStubMethodHandler<>({{methodName}}Func));
             BiConsumer<{{inputType}}, StreamObserver<{{outputType}}>> {{methodName}}AsyncFunc = syncToAsync(this::{{methodName}});
             handlers.put({{methodName}}ProxyAsyncMethod.getMethodName(), new UnaryStubMethodHandler<>({{methodName}}AsyncFunc));
         {{/unaryMethods}}
-
         {{#serverStreamingMethods}}
             handlers.put({{methodName}}Method.getMethodName(), new ServerStreamMethodHandler<>(this::{{methodName}}));
         {{/serverStreamingMethods}}
-
         {{#clientStreamingMethods}}
             handlers.put({{methodName}}Method.getMethodName(), new BiStreamMethodHandler<>(this::{{methodName}}));
         {{/clientStreamingMethods}}
-
         {{#biStreamingWithoutClientStreamMethods}}
             handlers.put({{methodName}}Method.getMethodName(), new BiStreamMethodHandler<>(this::{{methodName}}));
         {{/biStreamingWithoutClientStreamMethods}}
 
             return new StubInvoker<>(this, url, {{interfaceClassName}}.class, handlers);
         }
-
-
     {{#unaryMethods}}
         @Override
         public {{outputType}} {{methodName}}({{inputType}} request){
             throw unimplementedMethodException({{methodName}}Method);
         }
-
     {{/unaryMethods}}
-
     {{#serverStreamingMethods}}
-        {{#javaDoc}}
-            {{{javaDoc}}}
-        {{/javaDoc}}
         @Override
         public void {{methodName}}({{inputType}} request, StreamObserver<{{outputType}}> responseObserver){
             throw unimplementedMethodException({{methodName}}Method);
         }
     {{/serverStreamingMethods}}
-
     {{#biStreamingWithoutClientStreamMethods}}
         @Override
         public StreamObserver<{{inputType}}> {{methodName}}(StreamObserver<{{outputType}}> responseObserver){
             throw unimplementedMethodException({{methodName}}Method);
         }
     {{/biStreamingWithoutClientStreamMethods}}
-
     {{#clientStreamingMethods}}
-        {{#javaDoc}}
-            {{{javaDoc}}}
-        {{/javaDoc}}
         @Override
         public StreamObserver<{{inputType}}> {{methodName}}(StreamObserver<{{outputType}}> responseObserver){
             throw unimplementedMethodException({{methodName}}Method);
@@ -319,5 +272,4 @@ public final class {{className}} {
                 "/" + serviceDescriptor.getInterfaceName() + "/" + methodDescriptor.getMethodName())).asException();
         }
     }
-
 }
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/annotations/GRequest.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/annotations/GRequest.java
new file mode 100644
index 0000000000..4e6144f908
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/stub/annotations/GRequest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.dubbo.rpc.stub.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface GRequest {
+    String value() default "";
+}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/GRequestArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/GRequestArgumentResolver.java
new file mode 100644
index 0000000000..7651892c6b
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/GRequestArgumentResolver.java
@@ -0,0 +1,85 @@
+/*
+ * 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.dubbo.rpc.protocol.tri.rest.support.basic;
+
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.common.utils.JsonUtils;
+import org.apache.dubbo.remoting.http12.HttpRequest;
+import org.apache.dubbo.remoting.http12.HttpResponse;
+import org.apache.dubbo.remoting.http12.exception.DecodeException;
+import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder;
+import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants;
+import org.apache.dubbo.rpc.protocol.tri.rest.argument.AnnotationBaseArgumentResolver;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta;
+import org.apache.dubbo.rpc.stub.annotations.GRequest;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+@Activate
+public class GRequestArgumentResolver implements AnnotationBaseArgumentResolver<GRequest> {
+
+    @Override
+    public Class<GRequest> accept() {
+        return GRequest.class;
+    }
+
+    @Override
+    public Object resolve(
+            ParameterMeta parameter, AnnotationMeta<GRequest> annotation, HttpRequest request, HttpResponse response) {
+        HttpMessageDecoder decoder = request.attribute(RestConstants.BODY_DECODER_ATTRIBUTE);
+        if (decoder == null) {
+            return null;
+        }
+
+        Map<String, Object> value = new HashMap<>();
+        Map<String, String> variableMap = request.attribute(RestConstants.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
+        if (variableMap != null) {
+            value.putAll(variableMap);
+        }
+
+        InputStream is = request.inputStream();
+        try {
+            int available = is.available();
+            if (available > 0) {
+                Object body = decoder.decode(is, Object.class, request.charsetOrDefault());
+                if (body instanceof Map) {
+                    @SuppressWarnings("unchecked")
+                    Map<String, Object> bodyMap = (Map<String, Object>) body;
+                    String key = annotation.getValue();
+                    if ("*".equals(key) || key.isEmpty()) {
+                        value.putAll(bodyMap);
+                    } else {
+                        value.put(key, bodyMap.get(key));
+                    }
+                }
+            }
+        } catch (IOException e) {
+            throw new DecodeException("Error reading input", e);
+        }
+
+        return decoder.decode(
+                new ByteArrayInputStream(JsonUtils.toJson(value).getBytes(StandardCharsets.UTF_8)),
+                parameter.getType(),
+                StandardCharsets.UTF_8);
+    }
+}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
index 0486f00884..829c373182 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
@@ -1,3 +1,4 @@
 misc=org.apache.dubbo.rpc.protocol.tri.rest.argument.MiscArgumentResolver
 basic-param=org.apache.dubbo.rpc.protocol.tri.rest.support.basic.ParamArgumentResolver
 basic-fallback=org.apache.dubbo.rpc.protocol.tri.rest.support.basic.FallbackArgumentResolver
+grpc-request=org.apache.dubbo.rpc.protocol.tri.rest.support.basic.GRequestArgumentResolver