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