You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by xu...@apache.org on 2026/08/06 02:10:23 UTC

(dubbo-go-samples) branch main updated: [feat] Add REST protocol samples (#1109)

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

Alanxtl pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/dubbo-go-samples.git


The following commit(s) were added to refs/heads/main by this push:
     new 4bb14f2e [feat] Add REST protocol samples (#1109)
4bb14f2e is described below

commit 4bb14f2ef83f36e68e30ab29f75f372d6c92182f
Author: Xuetao Li <m1...@163.com>
AuthorDate: Thu Aug 6 02:10:17 2026 +0000

    [feat] Add REST protocol samples (#1109)
    
    * add rest samples
    
    * fix mod
    
    * detete useless
    
    * Replace fmt.Errorf with logger.Errorf for errors
    
    * fix
    
    * import format
    
    * add panic
    
    * import
---
 README.md                        |   1 +
 README_CN.md                     |   1 +
 rpc/rest/README.md               |  76 +++++++++++++++++++
 rpc/rest/README_CN.md            |  88 ++++++++++++++++++++++
 rpc/rest/api/greeting.go         |  41 +++++++++++
 rpc/rest/api/registry.go         |  71 ++++++++++++++++++
 rpc/rest/api/rest_config.go      |  63 ++++++++++++++++
 rpc/rest/go-client/cmd/client.go | 122 +++++++++++++++++++++++++++++++
 rpc/rest/go-server/cmd/server.go | 154 +++++++++++++++++++++++++++++++++++++++
 start_integrate_test.sh          |   1 +
 10 files changed, 618 insertions(+)

diff --git a/README.md b/README.md
index 10ecbdd0..d6923e27 100644
--- a/README.md
+++ b/README.md
@@ -70,6 +70,7 @@ Please refer to [HOWTO.md](HOWTO.md) for detailed instructions on running the sa
   * `rpc/jsonrpc`: JSON-RPC protocol example.
   * `rpc/triple`: Triple protocol example with multiple serialization formats.
   * `rpc/triple/openapi`: Demonstrates how to enable OpenAPI documentation for Triple protocol services, including versioned services and non-IDL services.
+  * `rpc/rest`: REST protocol example.
 * `streaming`: Streaming RPC example, also includes Go–Java interoperability when both use streaming.
 * `task`: Task scheduling and execution example.
 * `timeout`: Demonstrates timeout handling in Dubbo-go.
diff --git a/README_CN.md b/README_CN.md
index a2084e6a..841d90c8 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -70,6 +70,7 @@
   * `rpc/jsonrpc`:基于 JSON-RPC 协议的示例。
   * `rpc/triple`:Triple 协议示例,涵盖多种序列化方式。
   * `rpc/triple/openapi`:演示如何为 Triple 协议服务启用 OpenAPI 文档,包括多版本服务和非 IDL 服务的注册。
+  * `rpc/rest`:基于 REST 协议的示例。
 * `streaming`:流式 RPC 调用示例,并包含了Dubbo-go与Dubbo-java同时使用流式传输的互操作示例。
 * `task`:任务调度与执行示例。
 * `timeout`:Dubbo-go 超时处理示例。
diff --git a/rpc/rest/README.md b/rpc/rest/README.md
new file mode 100644
index 00000000..fc7c1712
--- /dev/null
+++ b/rpc/rest/README.md
@@ -0,0 +1,76 @@
+# Dubbo-Go REST sample
+
+This sample validates the Dubbo-Go REST protocol with direct URL, interface-level registry, and application-level service discovery. It uses an explicit REST mapping for path, query, header, and body arguments.
+
+## Run
+
+Start the provider with the default Nacos application-level service discovery mode:
+
+```bash
+go run ./rpc/rest/go-server/cmd
+```
+
+Run the Dubbo-Go REST consumer in another terminal:
+
+```bash
+go run ./rpc/rest/go-client/cmd
+```
+
+Expected client output:
+
+```text
+REST response: userID=101 name=dubbo-go traceID=trace-rest-basic message=body-from-dubbo-rest-client greeting="hello dubbo-go, userID=101, traceID=trace-rest-basic, message=body-from-dubbo-rest-client"
+```
+
+The same provider is also a normal HTTP endpoint:
+
+```bash
+curl -s \
+  -X POST 'http://127.0.0.1:20080/api/v1/users/202/greeting?name=curl' \
+  -H 'Content-Type: application/json' \
+  -H 'Accept: application/json' \
+  -H 'X-Trace-ID: trace-curl' \
+  -d '{"message":"body-from-curl"}'
+```
+
+## What this proves
+
+The provider URL identifies the Dubbo service endpoint (address + interface), for example `rest://127.0.0.1:20080/org.apache.dubbo.samples.rest.GreetingService`.
+
+The actual REST call shape comes from the REST method config in `api/rest_config.go`:
+
+- method: `POST`
+- path: `/api/v1/users/{userID}/greeting`
+- path parameter: argument `0` -> `userID`
+- query parameter: argument `1` -> `name`
+- header: argument `2` -> `X-Trace-ID`
+- body: argument `3`
+
+So this sample intentionally separates "provider address" from "REST HTTP mapping".
+
+## Registry service discovery
+
+The provider and consumer support these flags:
+
+- `-registry=direct|zookeeper|nacos`
+- `-registry-type=interface|service|all`
+
+The default is `-registry=nacos -registry-type=service`.
+
+`interface` means the registry stores the callable `rest://...` provider URL directly. `service` means the registry stores the application instance; the consumer finds the application by service mapping, fetches metadata from the provider metadata service, then reconstructs the `rest://...` provider URL from the discovered instance and service metadata. `all` registers both forms.
+
+Run direct URL mode:
+
+```bash
+go run ./rpc/rest/go-server/cmd -registry=direct
+go run ./rpc/rest/go-client/cmd -registry=direct
+```
+
+Run ZooKeeper interface-level registration:
+
+```bash
+go run ./rpc/rest/go-server/cmd -registry=zookeeper -registry-type=interface
+go run ./rpc/rest/go-client/cmd -registry=zookeeper -registry-type=interface
+```
+
+All modes should print the same `REST response: ...` line from the consumer.
diff --git a/rpc/rest/README_CN.md b/rpc/rest/README_CN.md
new file mode 100644
index 00000000..dbccb2c6
--- /dev/null
+++ b/rpc/rest/README_CN.md
@@ -0,0 +1,88 @@
+# Dubbo-Go REST 示例
+
+这个示例用于验证 Dubbo-Go REST 协议的三种地址获取方式:
+
+- 直连 URL
+- 接口级注册
+- 应用级服务发现
+
+示例通过本地 REST 配置显式声明 HTTP 方法、路径参数、查询参数、请求头和请求体映射。
+
+## 运行
+
+默认模式是 Nacos 应用级服务发现:
+
+```bash
+go run ./rpc/rest/go-server/cmd
+```
+
+在另一个终端运行消费者:
+
+```bash
+go run ./rpc/rest/go-client/cmd
+```
+
+期望输出:
+
+```text
+REST response: userID=101 name=dubbo-go traceID=trace-rest-basic message=body-from-dubbo-rest-client greeting="hello dubbo-go, userID=101, traceID=trace-rest-basic, message=body-from-dubbo-rest-client"
+```
+
+Provider 同时也是一个普通 HTTP 服务,可以直接用 `curl` 调用:
+
+```bash
+curl -s \
+  -X POST 'http://127.0.0.1:20080/api/v1/users/202/greeting?name=curl' \
+  -H 'Content-Type: application/json' \
+  -H 'Accept: application/json' \
+  -H 'X-Trace-ID: trace-curl' \
+  -d '{"message":"body-from-curl"}'
+```
+
+## REST 映射
+
+Provider URL 用于标识可调用的 Dubbo 服务端点(地址 + 接口),例如:`rest://127.0.0.1:20080/org.apache.dubbo.samples.rest.GreetingService`
+
+真正的 REST 调用形态来自 `api/rest_config.go`:
+
+- HTTP 方法:`POST`
+- 路径:`/api/v1/users/{userID}/greeting`
+- 路径参数:参数 `0` -> `userID`
+- 查询参数:参数 `1` -> `name`
+- 请求头:参数 `2` -> `X-Trace-ID`
+- 请求体:参数 `3`
+
+也就是说,这个示例刻意区分了“服务地址发现”和“REST HTTP 映射”。
+
+## 服务发现模式
+
+Provider 和 Consumer 都支持以下参数:
+
+- `-registry=direct|zookeeper|nacos`
+- `-registry-type=interface|service|all`
+
+默认值是:
+
+```bash
+-registry=nacos -registry-type=service
+```
+
+`interface` 表示注册中心直接保存可调用的 `rest://...` provider URL。  
+`service` 表示注册中心保存应用实例;consumer 先通过 service mapping 找到应用,再通过 metadata service 获取服务元数据,最后根据应用实例和 `ServiceInfo` 重建 `rest://...` provider URL。  
+`all` 表示同时注册接口级和应用级两种数据。
+
+## 直连模式
+
+```bash
+go run ./rpc/rest/go-server/cmd -registry=direct
+go run ./rpc/rest/go-client/cmd -registry=direct
+```
+
+## ZooKeeper 接口级注册
+
+```bash
+go run ./rpc/rest/go-server/cmd -registry=zookeeper -registry-type=interface
+go run ./rpc/rest/go-client/cmd -registry=zookeeper -registry-type=interface
+```
+
+所有模式下,consumer 都应该输出相同的 `REST response: ...`。
diff --git a/rpc/rest/api/greeting.go b/rpc/rest/api/greeting.go
new file mode 100644
index 00000000..277329fc
--- /dev/null
+++ b/rpc/rest/api/greeting.go
@@ -0,0 +1,41 @@
+/*
+ * 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 api
+
+const (
+	InterfaceName     = "org.apache.dubbo.samples.rest.GreetingService"
+	MethodGetGreeting = "GetGreeting"
+)
+
+type GreetingService struct{}
+
+func (*GreetingService) Reference() string {
+	return InterfaceName
+}
+
+type GreetingRequest struct {
+	Message string `json:"message"`
+}
+
+type GreetingResponse struct {
+	UserID   int    `json:"userID"`
+	Name     string `json:"name"`
+	TraceID  string `json:"traceID"`
+	Message  string `json:"message"`
+	Greeting string `json:"greeting"`
+}
diff --git a/rpc/rest/api/registry.go b/rpc/rest/api/registry.go
new file mode 100644
index 00000000..a95eec21
--- /dev/null
+++ b/rpc/rest/api/registry.go
@@ -0,0 +1,71 @@
+/*
+ * 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 api
+
+import (
+	"fmt"
+)
+
+import (
+	"dubbo.apache.org/dubbo-go/v3/registry"
+)
+
+const (
+	ServerAppName = "dubbo_rest_basic_server"
+	ClientAppName = "dubbo_rest_basic_client"
+
+	RegistryDirect    = "direct"
+	RegistryZookeeper = "zookeeper"
+	RegistryNacos     = "nacos"
+
+	RegistryTypeInterface = "interface"
+	RegistryTypeService   = "service"
+	RegistryTypeAll       = "all"
+
+	DefaultRegistry     = RegistryNacos
+	DefaultRegistryType = RegistryTypeService
+)
+
+func RegistryOptions(name string, registryType string) ([]registry.Option, error) {
+	if name == RegistryDirect || name == "" {
+		return nil, nil
+	}
+
+	var opts []registry.Option
+	switch name {
+	case RegistryZookeeper:
+		opts = append(opts, registry.WithZookeeper(), registry.WithAddress("127.0.0.1:2181"))
+	case RegistryNacos:
+		opts = append(opts, registry.WithNacos(), registry.WithAddress("127.0.0.1:8848"))
+	default:
+		return nil, fmt.Errorf("unsupported registry %q, use direct, zookeeper, or nacos", name)
+	}
+
+	opts = append(opts, registry.WithoutUseAsConfigCenter())
+	switch registryType {
+	case "", RegistryTypeInterface:
+		opts = append(opts, registry.WithRegisterInterface())
+	case RegistryTypeService:
+		opts = append(opts, registry.WithRegisterService())
+	case RegistryTypeAll:
+		opts = append(opts, registry.WithRegisterServiceAndInterface())
+	default:
+		return nil, fmt.Errorf("unsupported registry type %q, use interface, service, or all", registryType)
+	}
+	return opts, nil
+}
diff --git a/rpc/rest/api/rest_config.go b/rpc/rest/api/rest_config.go
new file mode 100644
index 00000000..28cf5d1a
--- /dev/null
+++ b/rpc/rest/api/rest_config.go
@@ -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 api
+
+import (
+	"net/http"
+)
+
+import (
+	"dubbo.apache.org/dubbo-go/v3/common/constant"
+	restconfig "dubbo.apache.org/dubbo-go/v3/protocol/rest/config"
+)
+
+const RestPath = "/api/v1/users/{userID}/greeting"
+
+func InstallProviderRestConfig() {
+	restconfig.SetRestProviderServiceConfigMap(map[string]*restconfig.RestServiceConfig{
+		InterfaceName: newRestServiceConfig(constant.DefaultRestServer, ""),
+	})
+}
+
+func InstallConsumerRestConfig() {
+	restconfig.SetRestConsumerServiceConfigMap(map[string]*restconfig.RestServiceConfig{
+		InterfaceName: newRestServiceConfig("", constant.DefaultRestClient),
+	})
+}
+
+func newRestServiceConfig(serverType string, clientType string) *restconfig.RestServiceConfig {
+	return &restconfig.RestServiceConfig{
+		InterfaceName: InterfaceName,
+		Server:        serverType,
+		Client:        clientType,
+		RestMethodConfigsMap: map[string]*restconfig.RestMethodConfig{
+			MethodGetGreeting: {
+				InterfaceName:  InterfaceName,
+				MethodName:     MethodGetGreeting,
+				Path:           RestPath,
+				MethodType:     http.MethodPost,
+				PathParamsMap:  map[int]string{0: "userID"},
+				QueryParamsMap: map[int]string{1: "name"},
+				HeadersMap:     map[int]string{2: "X-Trace-ID"},
+				Body:           3,
+				Consumes:       "application/json",
+				Produces:       "application/json",
+			},
+		},
+	}
+}
diff --git a/rpc/rest/go-client/cmd/client.go b/rpc/rest/go-client/cmd/client.go
new file mode 100644
index 00000000..1ce50b2c
--- /dev/null
+++ b/rpc/rest/go-client/cmd/client.go
@@ -0,0 +1,122 @@
+/*
+ * 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 main
+
+import (
+	"context"
+	"flag"
+	"fmt"
+	"time"
+)
+
+import (
+	"dubbo.apache.org/dubbo-go/v3"
+	"dubbo.apache.org/dubbo-go/v3/client"
+	_ "dubbo.apache.org/dubbo-go/v3/cluster/cluster/failover"
+	_ "dubbo.apache.org/dubbo-go/v3/cluster/cluster/zoneaware"
+	_ "dubbo.apache.org/dubbo-go/v3/cluster/loadbalance/random"
+	_ "dubbo.apache.org/dubbo-go/v3/cluster/router/condition"
+	"dubbo.apache.org/dubbo-go/v3/common/constant"
+	_ "dubbo.apache.org/dubbo-go/v3/filter/graceful_shutdown"
+	_ "dubbo.apache.org/dubbo-go/v3/metadata/mapping/metadata"
+	_ "dubbo.apache.org/dubbo-go/v3/metadata/report/nacos"
+	_ "dubbo.apache.org/dubbo-go/v3/metadata/report/zookeeper"
+	_ "dubbo.apache.org/dubbo-go/v3/protocol/dubbo"
+	_ "dubbo.apache.org/dubbo-go/v3/protocol/rest"
+	_ "dubbo.apache.org/dubbo-go/v3/registry/directory"
+	_ "dubbo.apache.org/dubbo-go/v3/registry/nacos"
+	_ "dubbo.apache.org/dubbo-go/v3/registry/protocol"
+	_ "dubbo.apache.org/dubbo-go/v3/registry/servicediscovery"
+	_ "dubbo.apache.org/dubbo-go/v3/registry/zookeeper"
+
+	"github.com/dubbogo/gost/log/logger"
+)
+
+import (
+	"github.com/apache/dubbo-go-samples/rpc/rest/api"
+)
+
+func main() {
+	registryName := flag.String("registry", api.DefaultRegistry, "registry mode: direct, zookeeper, or nacos")
+	registryType := flag.String("registry-type", api.DefaultRegistryType, "registry type: interface, service, or all")
+	flag.Parse()
+
+	api.InstallConsumerRestConfig()
+
+	instanceOpts := []dubbo.InstanceOption{
+		dubbo.WithName(api.ClientAppName),
+	}
+	registryOpts, err := api.RegistryOptions(*registryName, *registryType)
+	if err != nil {
+		panic(err)
+	}
+	if len(registryOpts) > 0 {
+		instanceOpts = append(instanceOpts, dubbo.WithRegistry(registryOpts...))
+	}
+
+	ins, err := dubbo.NewInstance(instanceOpts...)
+	if err != nil {
+		panic(err)
+	}
+
+	clientOpts := []client.ClientOption{
+		client.WithClientNoCheck(),
+		client.WithClientRequestTimeout(5 * time.Second),
+	}
+	if *registryName == api.RegistryDirect || *registryName == "" {
+		clientOpts = append(clientOpts, client.WithClientURL("rest://127.0.0.1:20080"))
+	}
+
+	cli, err := ins.NewClient(clientOpts...)
+	if err != nil {
+		panic(err)
+	}
+
+	conn, err := cli.Dial(
+		api.InterfaceName,
+		client.WithProtocol(constant.RESTProtocol),
+	)
+	if err != nil {
+		panic(err)
+	}
+
+	resp := new(api.GreetingResponse)
+	err = conn.CallUnary(
+		context.Background(),
+		[]any{
+			101,
+			"dubbo-go",
+			"trace-rest-basic",
+			&api.GreetingRequest{Message: "body-from-dubbo-rest-client"},
+		},
+		resp,
+		api.MethodGetGreeting,
+	)
+	if err != nil {
+		logger.Error(err)
+		panic(err)
+	}
+
+	if resp.UserID != 101 || resp.Name != "dubbo-go" || resp.TraceID != "trace-rest-basic" {
+		panic(fmt.Errorf("gets an unexpected result: userID=%d name=%s traceID=%s message=%s greeting=%q",
+			resp.UserID, resp.Name, resp.TraceID, resp.Message, resp.Greeting))
+	}
+
+	logger.Infof("REST response: userID=%d name=%s traceID=%s message=%s greeting=%q\n",
+		resp.UserID, resp.Name, resp.TraceID, resp.Message, resp.Greeting)
+}
diff --git a/rpc/rest/go-server/cmd/server.go b/rpc/rest/go-server/cmd/server.go
new file mode 100644
index 00000000..e57b440e
--- /dev/null
+++ b/rpc/rest/go-server/cmd/server.go
@@ -0,0 +1,154 @@
+/*
+ * 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 main
+
+import (
+	"context"
+	"flag"
+	"fmt"
+	"strconv"
+)
+
+import (
+	"dubbo.apache.org/dubbo-go/v3"
+	_ "dubbo.apache.org/dubbo-go/v3/filter/echo"
+	_ "dubbo.apache.org/dubbo-go/v3/metadata/mapping/metadata"
+	_ "dubbo.apache.org/dubbo-go/v3/metadata/report/nacos"
+	_ "dubbo.apache.org/dubbo-go/v3/metadata/report/zookeeper"
+	"dubbo.apache.org/dubbo-go/v3/protocol"
+	_ "dubbo.apache.org/dubbo-go/v3/protocol/dubbo"
+	_ "dubbo.apache.org/dubbo-go/v3/protocol/rest"
+	_ "dubbo.apache.org/dubbo-go/v3/proxy/proxy_factory"
+	_ "dubbo.apache.org/dubbo-go/v3/registry/nacos"
+	_ "dubbo.apache.org/dubbo-go/v3/registry/protocol"
+	_ "dubbo.apache.org/dubbo-go/v3/registry/servicediscovery"
+	_ "dubbo.apache.org/dubbo-go/v3/registry/zookeeper"
+	dubboserver "dubbo.apache.org/dubbo-go/v3/server"
+
+	"github.com/dubbogo/gost/log/logger"
+)
+
+import (
+	"github.com/apache/dubbo-go-samples/rpc/rest/api"
+)
+
+type GreetingProvider struct {
+	api.GreetingService
+}
+
+func (p *GreetingProvider) GetGreeting(_ context.Context, args []any) (*api.GreetingResponse, error) {
+	if len(args) != 4 {
+		return nil, fmt.Errorf("expected 4 REST arguments, got %d", len(args))
+	}
+
+	userID, err := toInt(args[0])
+	if err != nil {
+		return nil, fmt.Errorf("parse userID: %w", err)
+	}
+
+	name := fmt.Sprint(args[1])
+	traceID := fmt.Sprint(args[2])
+	message := bodyMessage(args[3])
+
+	return &api.GreetingResponse{
+		UserID:   userID,
+		Name:     name,
+		TraceID:  traceID,
+		Message:  message,
+		Greeting: fmt.Sprintf("hello %s, userID=%d, traceID=%s, message=%s", name, userID, traceID, message),
+	}, nil
+}
+
+func toInt(v any) (int, error) {
+	switch val := v.(type) {
+	case int:
+		return val, nil
+	case int32:
+		return int(val), nil
+	case int64:
+		return int(val), nil
+	case string:
+		return strconv.Atoi(val)
+	default:
+		return strconv.Atoi(fmt.Sprint(val))
+	}
+}
+
+func bodyMessage(v any) string {
+	switch body := v.(type) {
+	case map[string]any:
+		return fmt.Sprint(body["message"])
+	case map[string]string:
+		return body["message"]
+	case *api.GreetingRequest:
+		return body.Message
+	case api.GreetingRequest:
+		return body.Message
+	default:
+		return fmt.Sprint(v)
+	}
+}
+
+func main() {
+	registryName := flag.String("registry", api.DefaultRegistry, "registry mode: direct, zookeeper, or nacos")
+	registryType := flag.String("registry-type", api.DefaultRegistryType, "registry type: interface, service, or all")
+	flag.Parse()
+
+	api.InstallProviderRestConfig()
+
+	instanceOpts := []dubbo.InstanceOption{
+		dubbo.WithName(api.ServerAppName),
+		dubbo.WithProtocol(
+			protocol.WithREST(),
+			protocol.WithIp("127.0.0.1"),
+			protocol.WithPort(20080),
+		),
+	}
+
+	registryOpts, err := api.RegistryOptions(*registryName, *registryType)
+	if err != nil {
+		panic(err)
+	}
+	if len(registryOpts) > 0 {
+		instanceOpts = append(instanceOpts, dubbo.WithRegistry(registryOpts...))
+	}
+
+	ins, err := dubbo.NewInstance(instanceOpts...)
+	if err != nil {
+		panic(err)
+	}
+
+	srv, err := ins.NewServer()
+	if err != nil {
+		panic(err)
+	}
+
+	if err := srv.RegisterService(
+		&GreetingProvider{},
+		dubboserver.WithInterface(api.InterfaceName),
+		dubboserver.WithFilter("echo"),
+	); err != nil {
+		panic(err)
+	}
+
+	logger.Infof("REST provider is listening on http://127.0.0.1:20080%s, registry=%s, registry-type=%s",
+		api.RestPath, *registryName, *registryType)
+	if err := srv.Serve(); err != nil {
+		panic(err)
+	}
+}
diff --git a/start_integrate_test.sh b/start_integrate_test.sh
index 649e46fe..2f17f0b5 100755
--- a/start_integrate_test.sh
+++ b/start_integrate_test.sh
@@ -71,6 +71,7 @@ array+=("rpc/triple/reflection")
 array+=("rpc/triple/registry")
 array+=("rpc/triple/stream")
 array+=("rpc/triple/openapi")
+array+=("rpc/rest")
 
 # tls
 array+=("tls")