From 2765ef0f5aee05b9534be237aa83b1b13f34acb9 Mon Sep 17 00:00:00 2001
From: Patrick <dreamlike.sky@foxmail.com>
Date: Sun, 19 Jan 2020 23:01:31 +0800
Subject: [PATCH] rest configs and invoker

---
 common/extension/rest_client.go               |  20 +++
 common/extension/rest_config_reader.go        |  24 ++++
 config/consumer_config.go                     |   1 +
 config/provider_config.go                     |   1 +
 go.mod                                        |   1 +
 go.sum                                        |   4 +
 protocol/rest/rest_client/resty_client.go     |  69 ++++++++++
 protocol/rest/rest_config_initializer.go      | 130 ++++++++++++++++++
 .../default_config_reader.go                  |  89 ++++++++++++
 protocol/rest/rest_interface/rest_client.go   |  25 ++++
 protocol/rest/rest_interface/rest_config.go   |  28 ++++
 .../rest/rest_interface/rest_config_reader.go |   6 +
 .../rest/rest_interface/rest_method_config.go |  17 +++
 protocol/rest/rest_invoker.go                 |  65 ++++++---
 protocol/rest/rest_invoker_test.go            |  70 ++++++++++
 protocol/rest/rest_protocol.go                |  27 +++-
 protocol/rest/rest_protocol_test.go           |  42 ++++++
 protocol/rest/{ => rest_server}/gin_server.go |   2 +-
 18 files changed, 600 insertions(+), 21 deletions(-)
 create mode 100644 common/extension/rest_client.go
 create mode 100644 common/extension/rest_config_reader.go
 create mode 100644 protocol/rest/rest_client/resty_client.go
 create mode 100644 protocol/rest/rest_config_initializer.go
 create mode 100644 protocol/rest/rest_config_reader/default_config_reader.go
 create mode 100644 protocol/rest/rest_interface/rest_client.go
 create mode 100644 protocol/rest/rest_interface/rest_config.go
 create mode 100644 protocol/rest/rest_interface/rest_config_reader.go
 create mode 100644 protocol/rest/rest_interface/rest_method_config.go
 create mode 100644 protocol/rest/rest_invoker_test.go
 create mode 100644 protocol/rest/rest_protocol_test.go
 rename protocol/rest/{ => rest_server}/gin_server.go (98%)

diff --git a/common/extension/rest_client.go b/common/extension/rest_client.go
new file mode 100644
index 000000000..35e6fb538
--- /dev/null
+++ b/common/extension/rest_client.go
@@ -0,0 +1,20 @@
+package extension
+
+import (
+	"github.com/apache/dubbo-go/protocol/rest/rest_interface"
+)
+
+var (
+	restClients = make(map[string]func(restOptions *rest_interface.RestOptions) rest_interface.RestClient)
+)
+
+func SetRestClient(name string, fun func(restOptions *rest_interface.RestOptions) rest_interface.RestClient) {
+	restClients[name] = fun
+}
+
+func GetRestClient(name string, restOptions *rest_interface.RestOptions) rest_interface.RestClient {
+	if restClients[name] == nil {
+		panic("restClient for " + name + " is not existing, make sure you have import the package.")
+	}
+	return restClients[name](restOptions)
+}
diff --git a/common/extension/rest_config_reader.go b/common/extension/rest_config_reader.go
new file mode 100644
index 000000000..be76689b9
--- /dev/null
+++ b/common/extension/rest_config_reader.go
@@ -0,0 +1,24 @@
+package extension
+
+import (
+	"github.com/apache/dubbo-go/protocol/rest/rest_interface"
+)
+
+var (
+	restConfigReaders = make(map[string]func() rest_interface.RestConfigReader)
+)
+
+func SetRestConfigReader(name string, fun func() rest_interface.RestConfigReader) {
+	restConfigReaders[name] = fun
+}
+
+func GetRestConfigReader(name string) rest_interface.RestConfigReader {
+	if name == "" {
+		name = "default"
+	}
+	if restConfigReaders[name] == nil {
+		panic("restConfigReaders for " + name + " is not existing, make sure you have import the package.")
+	}
+	return restConfigReaders[name]()
+
+}
diff --git a/config/consumer_config.go b/config/consumer_config.go
index 72f60b5f7..38b05c617 100644
--- a/config/consumer_config.go
+++ b/config/consumer_config.go
@@ -58,6 +58,7 @@ type ConsumerConfig struct {
 	ProtocolConf   interface{}                 `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf"`
 	FilterConf     interface{}                 `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" `
 	ShutdownConfig *ShutdownConfig             `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" `
+	RestConfigType string                      `default:"default" yaml:"rest_config_type" json:"rest_config_type,omitempty" property:"rest_config_type"`
 }
 
 func (c *ConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
diff --git a/config/provider_config.go b/config/provider_config.go
index 0fed44c81..4958bd7ab 100644
--- a/config/provider_config.go
+++ b/config/provider_config.go
@@ -50,6 +50,7 @@ type ProviderConfig struct {
 	ProtocolConf      interface{}                `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf" `
 	FilterConf        interface{}                `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" `
 	ShutdownConfig    *ShutdownConfig            `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" `
+	RestConfigType    string                     `default:"default" yaml:"rest_config_type" json:"rest_config_type,omitempty" property:"rest_config_type"`
 }
 
 func (c *ProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
diff --git a/go.mod b/go.mod
index 80987daec..efe505bcd 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,7 @@ require (
 	github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
 	github.com/gin-gonic/gin v1.5.0
 	github.com/go-errors/errors v1.0.1 // indirect
+	github.com/go-resty/resty/v2 v2.1.0
 	github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
 	github.com/golang/mock v1.3.1
 	github.com/google/btree v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 6ff826ba5..f16edfcf9 100644
--- a/go.sum
+++ b/go.sum
@@ -143,6 +143,8 @@ github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotf
 github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
 github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
 github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
+github.com/go-resty/resty/v2 v2.1.0 h1:Z6IefCpUMfnvItVJaJXWv/pMiiD11So35QgwEELsldE=
+github.com/go-resty/resty/v2 v2.1.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
 github.com/go-sql-driver/mysql v0.0.0-20180618115901-749ddf1598b4 h1:1LlmVz15APoKz9dnm5j2ePptburJlwEH+/v/pUuoxck=
 github.com/go-sql-driver/mysql v0.0.0-20180618115901-749ddf1598b4/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -507,6 +509,8 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/oauth2 v0.0.0-20170807180024-9a379c6b3e95/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
diff --git a/protocol/rest/rest_client/resty_client.go b/protocol/rest/rest_client/resty_client.go
new file mode 100644
index 000000000..de6f075bb
--- /dev/null
+++ b/protocol/rest/rest_client/resty_client.go
@@ -0,0 +1,69 @@
+package rest_client
+
+import (
+	"context"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/protocol/rest/rest_interface"
+	"github.com/go-resty/resty/v2"
+	"net"
+	"net/http"
+	"path"
+	"time"
+)
+
+const (
+	RESTY = "resty"
+)
+
+func init() {
+	extension.SetRestClient(RESTY, GetRestyClient)
+}
+
+var restyClient *RestyClient
+
+type RestyClient struct {
+	client *resty.Client
+}
+
+func NewRestyClient(restOption *rest_interface.RestOptions) *RestyClient {
+	if restOption.ConnectTimeout == 0 {
+		restOption.ConnectTimeout = 3
+	}
+	if restOption.RequestTimeout == 0 {
+		restOption.RequestTimeout = 3
+	}
+	client := resty.New()
+	client.SetTransport(
+		&http.Transport{
+			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
+				c, err := net.DialTimeout(network, addr, restOption.ConnectTimeout*time.Second)
+				if err != nil {
+					return nil, err
+				}
+				err = c.SetDeadline(time.Now().Add(restOption.RequestTimeout * time.Second))
+				if err != nil {
+					return nil, err
+				}
+				return c, nil
+			},
+		})
+	return &RestyClient{
+		client: client,
+	}
+}
+
+func (rc *RestyClient) Do(restRequest *rest_interface.RestRequest, res interface{}) error {
+	_, err := rc.client.R().
+		SetHeader("Content-Type", restRequest.Consumes).
+		SetHeader("Accept", restRequest.Produces).
+		SetPathParams(restRequest.PathParams).
+		SetQueryParams(restRequest.QueryParams).
+		SetBody(restRequest.Body).
+		SetResult(res).
+		Execute(restRequest.Method, "http://"+path.Join(restRequest.Location, restRequest.Path))
+	return err
+}
+
+func GetRestyClient(restOptions *rest_interface.RestOptions) rest_interface.RestClient {
+	return NewRestyClient(restOptions)
+}
diff --git a/protocol/rest/rest_config_initializer.go b/protocol/rest/rest_config_initializer.go
new file mode 100644
index 000000000..8dc8d340b
--- /dev/null
+++ b/protocol/rest/rest_config_initializer.go
@@ -0,0 +1,130 @@
+package rest
+
+import (
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/config"
+	_ "github.com/apache/dubbo-go/protocol/rest/rest_config_reader"
+	"github.com/apache/dubbo-go/protocol/rest/rest_interface"
+	"strconv"
+	"strings"
+)
+
+var (
+	restConsumerConfig           *rest_interface.RestConsumerConfig
+	restProviderConfig           *rest_interface.RestProviderConfig
+	restConsumerServiceConfigMap map[string]*rest_interface.RestConfig
+	restProviderServiceConfigMap map[string]*rest_interface.RestConfig
+)
+
+func init() {
+	initConsumerRestConfig()
+	initProviderRestConfig()
+}
+
+func initConsumerRestConfig() {
+	consumerConfigType := config.GetConsumerConfig().RestConfigType
+	consumerConfigReader := extension.GetRestConfigReader(consumerConfigType)
+	restConsumerConfig = consumerConfigReader.ReadConsumerConfig()
+	if restConsumerConfig == nil {
+		return
+	}
+	for _, rc := range restConsumerConfig.RestConfigMap {
+		rc.Client = getNotEmptyStr(rc.Client, restConsumerConfig.Client, "resty")
+		rc.RestMethodConfigsMap = initMethodConfigMap(rc, restConsumerConfig.Consumes, restConsumerConfig.Produces)
+		restConsumerServiceConfigMap[rc.InterfaceName] = rc
+	}
+}
+
+func initProviderRestConfig() {
+	providerConfigType := config.GetProviderConfig().RestConfigType
+	providerConfigReader := extension.GetRestConfigReader(providerConfigType)
+	restProviderConfig = providerConfigReader.ReadProviderConfig()
+	if restProviderConfig == nil {
+		return
+	}
+	for _, rc := range restProviderConfig.RestConfigMap {
+		rc.Server = getNotEmptyStr(rc.Server, restProviderConfig.Server)
+		rc.RestMethodConfigsMap = initMethodConfigMap(rc, restProviderConfig.Consumes, restProviderConfig.Produces)
+		restProviderServiceConfigMap[rc.InterfaceName] = rc
+	}
+}
+
+func initMethodConfigMap(rc *rest_interface.RestConfig, consumes string, produces string) map[string]*rest_interface.RestMethodConfig {
+	mcm := make(map[string]*rest_interface.RestMethodConfig, len(rc.RestMethodConfigs))
+	for _, mc := range rc.RestMethodConfigs {
+		mc.InterfaceName = rc.InterfaceName
+		mc.Path = rc.Path + mc.Path
+		mc.Consumes = getNotEmptyStr(mc.Consumes, rc.Consumes, consumes)
+		mc.Produces = getNotEmptyStr(mc.Produces, rc.Produces, produces)
+		mc.MethodType = getNotEmptyStr(mc.MethodType, rc.MethodType)
+		mc = transformMethodConfig(mc)
+		mcm[mc.MethodName] = mc
+	}
+	return mcm
+}
+
+func getNotEmptyStr(args ...string) string {
+	var r string
+	for _, t := range args {
+		if len(r) == 0 {
+			r = t
+		} else {
+			break
+		}
+	}
+	return r
+}
+
+func transformMethodConfig(methodConfig *rest_interface.RestMethodConfig) *rest_interface.RestMethodConfig {
+	if len(methodConfig.PathParamsMap) == 0 && len(methodConfig.PathParams) > 0 {
+		paramsMap, err := parseParamsString2Map(methodConfig.PathParams)
+		if err != nil {
+			logger.Warnf("[Rest Config] Path Param parse error:%v", err)
+		} else {
+			methodConfig.PathParamsMap = paramsMap
+		}
+	}
+	if len(methodConfig.QueryParamsMap) == 0 && len(methodConfig.QueryParams) > 0 {
+		paramsMap, err := parseParamsString2Map(methodConfig.PathParams)
+		if err != nil {
+			logger.Warnf("[Rest Config] Argument Param parse error:%v", err)
+		} else {
+			methodConfig.QueryParamsMap = paramsMap
+		}
+	}
+	if len(methodConfig.BodyMap) == 0 && len(methodConfig.Body) > 0 {
+		paramsMap, err := parseParamsString2Map(methodConfig.Body)
+		if err != nil {
+			logger.Warnf("[Rest Config] Body Param parse error:%v", err)
+		} else {
+			methodConfig.BodyMap = paramsMap
+		}
+	}
+	return methodConfig
+}
+
+func parseParamsString2Map(params string) (map[int]string, error) {
+	m := make(map[int]string)
+	for _, p := range strings.Split(params, ",") {
+		pa := strings.Split(p, ":")
+		key, err := strconv.Atoi(pa[0])
+		if err != nil {
+			return nil, err
+		}
+		m[key] = pa[1]
+	}
+	return m, nil
+}
+
+func GetRestConsumerServiceConfig(service string) *rest_interface.RestConfig {
+	return restConsumerServiceConfigMap[service]
+}
+
+func GetRestProviderServiceConfig(service string) *rest_interface.RestConfig {
+	return restProviderServiceConfigMap[service]
+}
+
+func SetRestConsumerServiceConfigMap(configMap map[string]*rest_interface.RestConfig) {
+	restConsumerServiceConfigMap = configMap
+}
diff --git a/protocol/rest/rest_config_reader/default_config_reader.go b/protocol/rest/rest_config_reader/default_config_reader.go
new file mode 100644
index 000000000..04b29b368
--- /dev/null
+++ b/protocol/rest/rest_config_reader/default_config_reader.go
@@ -0,0 +1,89 @@
+package rest_config_reader
+
+import (
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
+	"github.com/apache/dubbo-go/protocol/rest/rest_interface"
+	perrors "github.com/pkg/errors"
+	"gopkg.in/yaml.v2"
+	"io/ioutil"
+	"os"
+	"path"
+)
+
+const (
+	DEFAULT_READER = "default"
+)
+
+var (
+	defaultConfigReader *DefaultConfigReader
+)
+
+func init() {
+	extension.SetRestConfigReader(DEFAULT_READER, GetDefaultConfigReader)
+}
+
+type DefaultConfigReader struct {
+}
+
+func NewDefaultConfigReader() *DefaultConfigReader {
+	return &DefaultConfigReader{}
+}
+
+func (dcr *DefaultConfigReader) ReadConsumerConfig() *rest_interface.RestConsumerConfig {
+	confConFile := os.Getenv(constant.CONF_CONSUMER_FILE_PATH)
+	if confConFile == "" {
+		logger.Warnf("rest consumer configure(consumer) file name is nil")
+		return nil
+	}
+	if path.Ext(confConFile) != ".yml" {
+		logger.Warnf("rest consumer configure file name{%v} suffix must be .yml", confConFile)
+		return nil
+	}
+	confFileStream, err := ioutil.ReadFile(confConFile)
+	if err != nil {
+		logger.Warnf("ioutil.ReadFile(file:%s) = error:%v", confConFile, perrors.WithStack(err))
+		return nil
+	}
+	restConsumerConfig := &rest_interface.RestConsumerConfig{}
+	err = yaml.Unmarshal(confFileStream, restConsumerConfig)
+	if err != nil {
+		logger.Warnf("yaml.Unmarshal() = error:%v", perrors.WithStack(err))
+		return nil
+	}
+	return restConsumerConfig
+}
+
+func (dcr *DefaultConfigReader) ReadProviderConfig() *rest_interface.RestProviderConfig {
+	confProFile := os.Getenv(constant.CONF_PROVIDER_FILE_PATH)
+	if len(confProFile) == 0 {
+		logger.Warnf("rest provider configure(provider) file name is nil")
+		return nil
+	}
+
+	if path.Ext(confProFile) != ".yml" {
+		logger.Warnf("rest provider configure file name{%v} suffix must be .yml", confProFile)
+		return nil
+	}
+	confFileStream, err := ioutil.ReadFile(confProFile)
+	if err != nil {
+		logger.Warnf("ioutil.ReadFile(file:%s) = error:%v", confProFile, perrors.WithStack(err))
+		return nil
+	}
+	restProviderConfig := &rest_interface.RestProviderConfig{}
+	err = yaml.Unmarshal(confFileStream, restProviderConfig)
+	if err != nil {
+		logger.Warnf("yaml.Unmarshal() = error:%v", perrors.WithStack(err))
+		return nil
+	}
+
+	return restProviderConfig
+}
+
+func GetDefaultConfigReader() rest_interface.RestConfigReader {
+	if defaultConfigReader == nil {
+		defaultConfigReader = NewDefaultConfigReader()
+	}
+	return defaultConfigReader
+}
diff --git a/protocol/rest/rest_interface/rest_client.go b/protocol/rest/rest_interface/rest_client.go
new file mode 100644
index 000000000..6d0515daa
--- /dev/null
+++ b/protocol/rest/rest_interface/rest_client.go
@@ -0,0 +1,25 @@
+package rest_interface
+
+import (
+	"time"
+)
+
+type RestOptions struct {
+	RequestTimeout time.Duration
+	ConnectTimeout time.Duration
+}
+
+type RestRequest struct {
+	Location    string
+	Path        string
+	Produces    string
+	Consumes    string
+	Method      string
+	PathParams  map[string]string
+	QueryParams map[string]string
+	Body        map[string]interface{}
+}
+
+type RestClient interface {
+	Do(request *RestRequest, res interface{}) error
+}
diff --git a/protocol/rest/rest_interface/rest_config.go b/protocol/rest/rest_interface/rest_config.go
new file mode 100644
index 000000000..1c368d509
--- /dev/null
+++ b/protocol/rest/rest_interface/rest_config.go
@@ -0,0 +1,28 @@
+package rest_interface
+
+type RestConfig struct {
+	InterfaceName        string              `required:"true"  yaml:"interface"  json:"interface,omitempty" property:"interface"`
+	Url                  string              `yaml:"url"  json:"url,omitempty" property:"url"`
+	Path                 string              `yaml:"rest_path"  json:"rest_path,omitempty" property:"rest_path"`
+	Produces             string              `yaml:"rest_produces"  json:"rest_produces,omitempty" property:"rest_produces"`
+	Consumes             string              `yaml:"rest_consumes"  json:"rest_consumes,omitempty" property:"rest_consumes"`
+	MethodType           string              `yaml:"rest_method"  json:"rest_method,omitempty" property:"rest_method"`
+	Client               string              `yaml:"rest_client" json:"rest_client,omitempty" property:"rest_client"`
+	Server               string              `yaml:"rest_server" json:"rest_server,omitempty" property:"rest_server"`
+	RestMethodConfigs    []*RestMethodConfig `yaml:"methods" json:"methods,omitempty" property:"methods"`
+	RestMethodConfigsMap map[string]*RestMethodConfig
+}
+
+type RestConsumerConfig struct {
+	Client        string                 `default:"resty" yaml:"rest_client" json:"rest_client,omitempty" property:"rest_client"`
+	Produces      string                 `yaml:"rest_produces"  json:"rest_produces,omitempty" property:"rest_produces"`
+	Consumes      string                 `yaml:"rest_consumes"  json:"rest_consumes,omitempty" property:"rest_consumes"`
+	RestConfigMap map[string]*RestConfig `yaml:"references" json:"references,omitempty" property:"references"`
+}
+
+type RestProviderConfig struct {
+	Server        string                 `default:"go-restful" yaml:"rest_server" json:"rest_server,omitempty" property:"rest_server"`
+	Produces      string                 `yaml:"rest_produces"  json:"rest_produces,omitempty" property:"rest_produces"`
+	Consumes      string                 `yaml:"rest_consumes"  json:"rest_consumes,omitempty" property:"rest_consumes"`
+	RestConfigMap map[string]*RestConfig `yaml:"services" json:"services,omitempty" property:"services"`
+}
diff --git a/protocol/rest/rest_interface/rest_config_reader.go b/protocol/rest/rest_interface/rest_config_reader.go
new file mode 100644
index 000000000..0dea65719
--- /dev/null
+++ b/protocol/rest/rest_interface/rest_config_reader.go
@@ -0,0 +1,6 @@
+package rest_interface
+
+type RestConfigReader interface {
+	ReadConsumerConfig() *RestConsumerConfig
+	ReadProviderConfig() *RestProviderConfig
+}
diff --git a/protocol/rest/rest_interface/rest_method_config.go b/protocol/rest/rest_interface/rest_method_config.go
new file mode 100644
index 000000000..c9430644a
--- /dev/null
+++ b/protocol/rest/rest_interface/rest_method_config.go
@@ -0,0 +1,17 @@
+package rest_interface
+
+type RestMethodConfig struct {
+	InterfaceName  string
+	MethodName     string `required:"true" yaml:"name"  json:"name,omitempty" property:"name"`
+	Url            string `yaml:"url"  json:"url,omitempty" property:"url"`
+	Path           string `yaml:"rest_path"  json:"rest_path,omitempty" property:"rest_path"`
+	Produces       string `yaml:"rest_produces"  json:"rest_produces,omitempty" property:"rest_produces"`
+	Consumes       string `yaml:"rest_consumes"  json:"rest_consumes,omitempty" property:"rest_consumes"`
+	MethodType     string `yaml:"rest_method"  json:"rest_method,omitempty" property:"rest_method"`
+	PathParams     string `yaml:"rest_path_params"  json:"rest_path_params,omitempty" property:"rest_path_params"`
+	PathParamsMap  map[int]string
+	QueryParams    string `yaml:"rest_query_params"  json:"rest_query_params,omitempty" property:"rest_query_params"`
+	QueryParamsMap map[int]string
+	Body           string `yaml:"rest_body"  json:"rest_body,omitempty" property:"rest_body"`
+	BodyMap        map[int]string
+}
diff --git a/protocol/rest/rest_invoker.go b/protocol/rest/rest_invoker.go
index 0580b8389..ca2260632 100644
--- a/protocol/rest/rest_invoker.go
+++ b/protocol/rest/rest_invoker.go
@@ -1,31 +1,62 @@
 package rest
 
 import (
+	"fmt"
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/logger"
 	"github.com/apache/dubbo-go/protocol"
-	"net/http"
+	invocation_impl "github.com/apache/dubbo-go/protocol/invocation"
+	"github.com/apache/dubbo-go/protocol/rest/rest_interface"
 )
 
 type RestInvoker struct {
 	protocol.BaseInvoker
-	client *http.Client
+	client              rest_interface.RestClient
+	restMethodConfigMap map[string]*rest_interface.RestMethodConfig
 }
 
-func NewRestInvoker() *RestInvoker {
-	return &RestInvoker{}
+func NewRestInvoker(url common.URL, client rest_interface.RestClient, restMethodConfig map[string]*rest_interface.RestMethodConfig) *RestInvoker {
+	return &RestInvoker{
+		BaseInvoker:         *protocol.NewBaseInvoker(url),
+		client:              client,
+		restMethodConfigMap: restMethodConfig,
+	}
 }
 
 func (ri *RestInvoker) Invoke(invocation protocol.Invocation) protocol.Result {
-	// TODO 棣栧厛锛屽皢鏈湴璋冪敤鍜孯eferRestConfig锛岀粨鍚堝湪涓€璧凤紝鍒拌繖涓€姝ュ畬鎴愪簡Service -> Rest鐨勭粦瀹氾紱
-	//      绗竴姝ュ畬鎴愮殑浜х墿鏄竴浠絤etadata鍜岃姹傚弬鏁帮紝鏈楠ゅ皢鍒╃敤metadata鍜岃姹傚弬鏁版潵鏋勯€爃ttp璇锋眰锛屽寘鎷弬鏁板簭鍒楀寲锛宧eader璁惧畾锛涘叾涓弬鏁板簭鍒楀寲鍙互鏄疛son锛屼篃鍙互鏄疿ML锛屽悗缁彲浠ユ墿灞曞埌澶氬獟浣撶瓑锛�
-	//      http client鍙戦€佽姹傘€傝姝ラ瑕佸厑璁哥敤鎴疯嚜瀹氫箟鍏惰繛鎺ュ弬鏁帮紝渚嬪瓒呮椂鏃堕棿绛夛紱
-	//var (
-	//	result protocol.RPCResult
-	//)
-	//req, err := http.NewRequest("method", "url", bytes.NewBuffer([]byte{}))
-	//resp, err := ri.client.Do(req)
-	//defer resp.Body.Close()
-	//body, err := ioutil.ReadAll(resp.Body)
-	//result.Rest = invocation.Reply()
-	//return &result
-	return nil
+	inv := invocation.(*invocation_impl.RPCInvocation)
+	methodConfig := ri.restMethodConfigMap[inv.MethodName()]
+	var result protocol.RPCResult
+	if methodConfig == nil {
+		logger.Errorf("[RestInvoker]Rest methodConfig:%s is nill", inv.MethodName())
+		return nil
+	}
+	pathParams := make(map[string]string)
+	queryParams := make(map[string]string)
+	bodyParams := make(map[string]interface{})
+	for key, value := range methodConfig.PathParamsMap {
+		pathParams[value] = fmt.Sprintf("%v", inv.Arguments()[key])
+	}
+	for key, value := range methodConfig.QueryParamsMap {
+		queryParams[value] = fmt.Sprintf("%v", inv.Arguments()[key])
+	}
+	for key, value := range methodConfig.BodyMap {
+		bodyParams[value] = inv.Arguments()[key]
+	}
+	req := &rest_interface.RestRequest{
+		Location:    ri.GetUrl().Location,
+		Produces:    methodConfig.Produces,
+		Consumes:    methodConfig.Consumes,
+		Method:      methodConfig.MethodType,
+		Path:        methodConfig.Path,
+		PathParams:  pathParams,
+		QueryParams: queryParams,
+		Body:        bodyParams,
+	}
+	result.Err = ri.client.Do(req, inv.Reply())
+	if result.Err == nil {
+		result.Rest = inv.Reply()
+	}
+	return &result
+
 }
diff --git a/protocol/rest/rest_invoker_test.go b/protocol/rest/rest_invoker_test.go
new file mode 100644
index 000000000..ee8ba7b6f
--- /dev/null
+++ b/protocol/rest/rest_invoker_test.go
@@ -0,0 +1,70 @@
+package rest
+
+import (
+	"context"
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/protocol/invocation"
+	"github.com/apache/dubbo-go/protocol/rest/rest_client"
+	_ "github.com/apache/dubbo-go/protocol/rest/rest_config_reader"
+	"github.com/apache/dubbo-go/protocol/rest/rest_interface"
+	"github.com/stretchr/testify/assert"
+	"testing"
+	"time"
+)
+
+type User struct {
+}
+
+func TestRestInvoker_Invoke(t *testing.T) {
+	// Refer
+	proto := GetRestProtocol()
+	url, err := common.NewURL(context.Background(), "rest://127.0.0.1:8888/com.ikurento.user.UserProvider?anyhost=true&"+
+		"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+
+		"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+
+		"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+
+		"side=provider&timeout=3000&timestamp=1556509797245")
+	assert.NoError(t, err)
+	con := config.ConsumerConfig{
+		ConnectTimeout: 1 * time.Second,
+		RequestTimeout: 1 * time.Second,
+		RestConfigType: "default",
+	}
+	config.SetConsumerConfig(con)
+	configMap := make(map[string]*rest_interface.RestConfig)
+	methodConfigMap := make(map[string]*rest_interface.RestMethodConfig)
+	methodConfigMap["GetUser"] = &rest_interface.RestMethodConfig{
+		InterfaceName:  "",
+		MethodName:     "GetUser",
+		Path:           "/GetUser",
+		Produces:       "application/json",
+		Consumes:       "application/json",
+		MethodType:     "GET",
+		PathParams:     "",
+		PathParamsMap:  nil,
+		QueryParams:    "",
+		QueryParamsMap: nil,
+		Body:           "",
+		BodyMap:        nil,
+	}
+	configMap["com.ikurento.user.UserProvider"] = &rest_interface.RestConfig{
+		RestMethodConfigsMap: methodConfigMap,
+	}
+	restClient := rest_client.GetRestyClient(&rest_interface.RestOptions{ConnectTimeout: 5 * time.Second, RequestTimeout: 5 * time.Second})
+	invoker := NewRestInvoker(url, restClient, methodConfigMap)
+	user := &User{}
+	inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("GetUser"),
+		invocation.WithArguments([]interface{}{"1", "username"}), invocation.WithReply(user))
+	invoker.Invoke(inv)
+
+	// make sure url
+	eq := invoker.GetUrl().URLEqual(url)
+	assert.True(t, eq)
+
+	// make sure invokers after 'Destroy'
+	invokersLen := len(proto.(*RestProtocol).Invokers())
+	assert.Equal(t, 1, invokersLen)
+	proto.Destroy()
+	invokersLen = len(proto.(*RestProtocol).Invokers())
+	assert.Equal(t, 0, invokersLen)
+}
diff --git a/protocol/rest/rest_protocol.go b/protocol/rest/rest_protocol.go
index 2f4e945aa..8801f1efe 100644
--- a/protocol/rest/rest_protocol.go
+++ b/protocol/rest/rest_protocol.go
@@ -2,13 +2,24 @@ package rest
 
 import (
 	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/constant"
+	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/config"
 	"github.com/apache/dubbo-go/protocol"
+	"github.com/apache/dubbo-go/protocol/rest/rest_interface"
+	"time"
 )
 
 var (
 	restProtocol *RestProtocol
 )
 
+const REST = "rest"
+
+func init() {
+	extension.SetProtocol(REST, GetRestProtocol)
+}
+
 type RestProtocol struct {
 	protocol.BaseProtocol
 }
@@ -22,18 +33,28 @@ func (rp *RestProtocol) Export(invoker protocol.Invoker) protocol.Exporter {
 	//      Server鍦‥xport鐨勬椂鍊欏苟涓嶅仛浠€涔堜簨鎯呫€備絾鏄湪鎺ュ彈鍒拌姹傜殑鏃跺€欙紝瀹冮渶瑕佽礋璐f墽琛屽弽搴忓垪鍖栫殑杩囩▼;
 	//      http server鏄竴涓娊璞¢殧绂诲眰銆傚畠鍐呴儴鍏佽浣跨敤beego鎴栬€単in鏉ヤ綔涓簑eb鏈嶅姟鍣紝鎺ユ敹璇锋眰锛岀敤鎴峰彲浠ユ墿灞曡嚜宸辩殑瀹炵幇锛�
 
-	// create gin_server
-	// save gin_server in map
 	return nil
 }
 
 func (rp *RestProtocol) Refer(url common.URL) protocol.Invoker {
 	// create rest_invoker
-	return nil
+	var requestTimeout = config.GetConsumerConfig().RequestTimeout
+
+	requestTimeoutStr := url.GetParam(constant.TIMEOUT_KEY, config.GetConsumerConfig().Request_Timeout)
+	connectTimeout := config.GetConsumerConfig().ConnectTimeout
+	if t, err := time.ParseDuration(requestTimeoutStr); err == nil {
+		requestTimeout = t
+	}
+	restConfig := GetRestConsumerServiceConfig(url.Service())
+	restClient := extension.GetRestClient(restConfig.Client, &rest_interface.RestOptions{RequestTimeout: requestTimeout, ConnectTimeout: connectTimeout})
+	invoker := NewRestInvoker(url, restClient, restConfig.RestMethodConfigsMap)
+	rp.SetInvokers(invoker)
+	return invoker
 }
 
 func (rp *RestProtocol) Destroy() {
 	// destroy rest_server
+	rp.BaseProtocol.Destroy()
 }
 
 func GetRestProtocol() protocol.Protocol {
diff --git a/protocol/rest/rest_protocol_test.go b/protocol/rest/rest_protocol_test.go
new file mode 100644
index 000000000..e77fde8ad
--- /dev/null
+++ b/protocol/rest/rest_protocol_test.go
@@ -0,0 +1,42 @@
+package rest
+
+import (
+	"context"
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/config"
+	"github.com/apache/dubbo-go/protocol/rest/rest_interface"
+	"github.com/stretchr/testify/assert"
+	"testing"
+	"time"
+)
+
+func TestRestProtocol_Refer(t *testing.T) {
+	// Refer
+	proto := GetRestProtocol()
+	url, err := common.NewURL(context.Background(), "rest://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+
+		"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+
+		"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+
+		"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+
+		"side=provider&timeout=3000&timestamp=1556509797245")
+	assert.NoError(t, err)
+	con := config.ConsumerConfig{
+		ConnectTimeout: 5 * time.Second,
+		RequestTimeout: 5 * time.Second,
+	}
+	config.SetConsumerConfig(con)
+	configMap := make(map[string]*rest_interface.RestConfig)
+	configMap["com.ikurento.user.UserProvider"] = &rest_interface.RestConfig{}
+	SetRestConsumerServiceConfigMap(configMap)
+	invoker := proto.Refer(url)
+
+	// make sure url
+	eq := invoker.GetUrl().URLEqual(url)
+	assert.True(t, eq)
+
+	// make sure invokers after 'Destroy'
+	invokersLen := len(proto.(*RestProtocol).Invokers())
+	assert.Equal(t, 1, invokersLen)
+	proto.Destroy()
+	invokersLen = len(proto.(*RestProtocol).Invokers())
+	assert.Equal(t, 0, invokersLen)
+}
diff --git a/protocol/rest/gin_server.go b/protocol/rest/rest_server/gin_server.go
similarity index 98%
rename from protocol/rest/gin_server.go
rename to protocol/rest/rest_server/gin_server.go
index b05792617..0525e2f83 100644
--- a/protocol/rest/gin_server.go
+++ b/protocol/rest/rest_server/gin_server.go
@@ -1,4 +1,4 @@
-package rest
+package rest_server
 
 import (
 	"context"
-- 
GitLab