Skip to content
Snippets Groups Projects
Commit dc19a15c authored by Patrick's avatar Patrick
Browse files

go restful server and some unit tests

parent f80903a7
No related branches found
No related tags found
No related merge requests found
Showing
with 357 additions and 93 deletions
......@@ -42,6 +42,7 @@ const (
DEFAULT_FAILBACK_TIMES_INT = 3
DEFAULT_FAILBACK_TASKS = 100
DEFAULT_REST_CLIENT = "resty"
DEFAULT_REST_SERVER = "go-restful"
)
const (
......
......@@ -8,7 +8,7 @@ var (
restClients = make(map[string]func(restOptions *rest_interface.RestOptions) rest_interface.RestClient)
)
func SetRestClientFunc(name string, fun func(restOptions *rest_interface.RestOptions) rest_interface.RestClient) {
func SetRestClient(name string, fun func(restOptions *rest_interface.RestOptions) rest_interface.RestClient) {
restClients[name] = fun
}
......
......@@ -8,7 +8,7 @@ var (
restServers = make(map[string]func() rest_interface.RestServer)
)
func SetRestServerFunc(name string, fun func() rest_interface.RestServer) {
func SetRestServer(name string, fun func() rest_interface.RestServer) {
restServers[name] = fun
}
......
......@@ -14,8 +14,8 @@ require (
github.com/creasty/defaults v1.3.0
github.com/dubbogo/getty v1.3.2
github.com/dubbogo/gost v1.5.2
github.com/emicklei/go-restful/v3 v3.0.0
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
......@@ -53,6 +53,7 @@ require (
go.etcd.io/etcd v3.3.13+incompatible
go.uber.org/atomic v1.4.0
go.uber.org/zap v1.10.0
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135
google.golang.org/grpc v1.22.1
gopkg.in/yaml.v2 v2.2.2
)
......
......@@ -112,6 +112,8 @@ github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIh
github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo=
github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0 h1:ZoRgc53qJCfSLimXqJDrmBhnt5GChDsExMCK7t48o0Y=
github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/emicklei/go-restful/v3 v3.0.0 h1:Duxxa4x0WIHW3bYEDmoAPNjmy8Rbqn+utcF74dlF/G8=
github.com/emicklei/go-restful/v3 v3.0.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.8.0 h1:uE6Fp4fOcAJdc1wTQXLJ+SYistkbG1dNoi6Zs1+Ybvk=
github.com/envoyproxy/go-control-plane v0.8.0/go.mod h1:GSSbY9P1neVhdY7G4wu+IK1rk/dqhiCC/4ExuWJZVuk=
github.com/envoyproxy/protoc-gen-validate v0.0.14 h1:YBW6/cKy9prEGRYLnaGa4IDhzxZhRCtKsax8srGKDnM=
......@@ -127,8 +129,6 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo=
......
......@@ -16,7 +16,7 @@ import (
)
func init() {
extension.SetRestClientFunc(constant.DEFAULT_REST_CLIENT, GetRestyClient)
extension.SetRestClient(constant.DEFAULT_REST_CLIENT, GetRestyClient)
}
type RestyClient struct {
......
......@@ -48,7 +48,7 @@ func initProviderRestConfig() {
return
}
for _, rc := range restProviderConfig.RestConfigMap {
rc.Server = getNotEmptyStr(rc.Server, restProviderConfig.Server)
rc.Server = getNotEmptyStr(rc.Server, restProviderConfig.Server, constant.DEFAULT_REST_SERVER)
rc.RestMethodConfigsMap = initMethodConfigMap(rc, restProviderConfig.Consumes, restProviderConfig.Produces)
restProviderServiceConfigMap[rc.InterfaceName] = rc
}
......@@ -96,14 +96,6 @@ func transformMethodConfig(methodConfig *rest_interface.RestMethodConfig) *rest_
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
}
......@@ -131,3 +123,7 @@ func GetRestProviderServiceConfig(service string) *rest_interface.RestConfig {
func SetRestConsumerServiceConfigMap(configMap map[string]*rest_interface.RestConfig) {
restConsumerServiceConfigMap = configMap
}
func SetRestProviderServiceConfigMap(configMap map[string]*rest_interface.RestConfig) {
restProviderServiceConfigMap = configMap
}
......@@ -17,7 +17,7 @@ type RestRequest struct {
Method string
PathParams map[string]string
QueryParams map[string]string
Body map[string]interface{}
Body interface{}
Headers map[string]string
}
......
......@@ -39,8 +39,7 @@ type RestMethodConfig struct {
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
Body int `yaml:"rest_body" json:"rest_body,omitempty" property:"rest_body"`
Headers string `yaml:"rest_headers" json:"rest_headers,omitempty" property:"rest_headers"`
HeadersMap map[int]string
}
......@@ -8,6 +8,6 @@ import (
type RestServer interface {
Start(url common.URL)
Deploy(invoker protocol.Invoker, restMethodConfig map[string]*RestMethodConfig)
Undeploy(restMethodConfig map[string]*RestMethodConfig)
Destory()
UnDeploy(restMethodConfig map[string]*RestMethodConfig)
Destroy()
}
......@@ -3,11 +3,11 @@ package rest
import (
"context"
"fmt"
perrors "github.com/pkg/errors"
)
import (
"github.com/apache/dubbo-go/common"
"github.com/apache/dubbo-go/common/logger"
"github.com/apache/dubbo-go/protocol"
invocation_impl "github.com/apache/dubbo-go/protocol/invocation"
"github.com/apache/dubbo-go/protocol/rest/rest_interface"
......@@ -30,15 +30,36 @@ func NewRestInvoker(url common.URL, client *rest_interface.RestClient, restMetho
func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
inv := invocation.(*invocation_impl.RPCInvocation)
methodConfig := ri.restMethodConfigMap[inv.MethodName()]
var result protocol.RPCResult
var (
result protocol.RPCResult
body interface{}
pathParams map[string]string
queryParams map[string]string
headers map[string]string
err error
)
if methodConfig == nil {
logger.Errorf("[RestInvoker]Rest methodConfig:%s is nil", inv.MethodName())
return nil
result.Err = perrors.Errorf("[RestInvoker]Rest methodConfig:%s is nil", inv.MethodName())
return &result
}
if pathParams, err = restStringMapTransform(methodConfig.PathParamsMap, inv.Arguments()); err != nil {
result.Err = err
return &result
}
if queryParams, err = restStringMapTransform(methodConfig.QueryParamsMap, inv.Arguments()); err != nil {
result.Err = err
return &result
}
if headers, err = restStringMapTransform(methodConfig.HeadersMap, inv.Arguments()); err != nil {
result.Err = err
return &result
}
if len(inv.Arguments()) > methodConfig.Body && methodConfig.Body > 0 {
body = inv.Arguments()[methodConfig.Body]
} else {
result.Err = perrors.Errorf("[Rest Invoke] Index %v is out of bundle", methodConfig.Body)
return &result
}
pathParams := restStringMapTransform(methodConfig.PathParamsMap, inv.Arguments())
queryParams := restStringMapTransform(methodConfig.QueryParamsMap, inv.Arguments())
headers := restStringMapTransform(methodConfig.HeadersMap, inv.Arguments())
bodyParams := restInterfaceMapTransform(methodConfig.BodyMap, inv.Arguments())
req := &rest_interface.RestRequest{
Location: ri.GetUrl().Location,
Produces: methodConfig.Produces,
......@@ -47,7 +68,7 @@ func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio
Path: methodConfig.Path,
PathParams: pathParams,
QueryParams: queryParams,
Body: bodyParams,
Body: body,
Headers: headers,
}
result.Err = ri.client.Do(req, inv.Reply())
......@@ -57,18 +78,14 @@ func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio
return &result
}
func restStringMapTransform(paramsMap map[int]string, args []interface{}) map[string]string {
func restStringMapTransform(paramsMap map[int]string, args []interface{}) (map[string]string, error) {
resMap := make(map[string]string, len(paramsMap))
for key, value := range paramsMap {
resMap[value] = fmt.Sprintf("%v", args[key])
}
return resMap
}
func restInterfaceMapTransform(paramsMap map[int]string, args []interface{}) map[string]interface{} {
resMap := make(map[string]interface{}, len(paramsMap))
for key, value := range paramsMap {
resMap[value] = args[key]
for k, v := range paramsMap {
if k > len(args)-1 || k < 0 {
resMap[v] = fmt.Sprint(args[k])
} else {
return nil, perrors.Errorf("[Rest Invoke] Index %v is out of bundle", k)
}
}
return resMap
return resMap, nil
}
......@@ -13,9 +13,6 @@ import (
"time"
)
type User struct {
}
func TestRestInvoker_Invoke(t *testing.T) {
// Refer
url, err := common.NewURL(context.Background(), "rest://127.0.0.1:8888/com.ikurento.user.UserProvider?anyhost=true&"+
......@@ -43,8 +40,7 @@ func TestRestInvoker_Invoke(t *testing.T) {
PathParamsMap: nil,
QueryParams: "",
QueryParamsMap: nil,
Body: "",
BodyMap: nil,
Body: -1,
}
configMap["com.ikurento.user.UserProvider"] = &rest_interface.RestConfig{
RestMethodConfigsMap: methodConfigMap,
......
......@@ -7,6 +7,7 @@ import (
"github.com/apache/dubbo-go/config"
"github.com/apache/dubbo-go/protocol"
"github.com/apache/dubbo-go/protocol/rest/rest_interface"
_ "github.com/apache/dubbo-go/protocol/rest/rest_server"
"strings"
"sync"
"time"
......@@ -103,7 +104,7 @@ func (rp *RestProtocol) Destroy() {
// destroy rest_server
rp.BaseProtocol.Destroy()
for key, server := range rp.serverMap {
server.Destory()
server.Destroy()
delete(rp.serverMap, key)
}
for key := range rp.clientMap {
......
......@@ -2,10 +2,15 @@ package rest
import (
"context"
"fmt"
"github.com/apache/dubbo-go/common"
"github.com/apache/dubbo-go/common/extension"
_ "github.com/apache/dubbo-go/common/proxy/proxy_factory"
"github.com/apache/dubbo-go/config"
"github.com/apache/dubbo-go/protocol/rest/rest_interface"
"github.com/stretchr/testify/assert"
"strings"
"sync"
"testing"
"time"
)
......@@ -42,3 +47,88 @@ func TestRestProtocol_Refer(t *testing.T) {
invokersLen = len(proto.(*RestProtocol).Invokers())
assert.Equal(t, 0, invokersLen)
}
func TestJsonrpcProtocol_Export(t *testing.T) {
// Export
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)
_, err = common.ServiceMap.Register(url.Protocol, &UserProvider{})
assert.NoError(t, err)
con := config.ProviderConfig{}
config.SetProviderConfig(con)
configMap := make(map[string]*rest_interface.RestConfig)
methodConfigMap := make(map[string]*rest_interface.RestMethodConfig)
queryParamsMap := make(map[int]string)
queryParamsMap[1] = "age"
queryParamsMap[2] = "name"
pathParamsMap := make(map[int]string)
pathParamsMap[0] = "userid"
methodConfigMap["GetUser"] = &rest_interface.RestMethodConfig{
InterfaceName: "",
MethodName: "GetUser",
Path: "/GetUser/{userid}",
Produces: "application/json",
Consumes: "application/json",
MethodType: "GET",
PathParams: "",
PathParamsMap: pathParamsMap,
QueryParams: "",
QueryParamsMap: queryParamsMap,
Body: -1,
}
configMap["com.ikurento.user.UserProvider"] = &rest_interface.RestConfig{
Server: "go-restful",
RestMethodConfigsMap: methodConfigMap,
}
SetRestProviderServiceConfigMap(configMap)
proxyFactory := extension.GetProxyFactory("default")
exporter := proto.Export(proxyFactory.GetInvoker(url))
// make sure url
eq := exporter.GetInvoker().GetUrl().URLEqual(url)
assert.True(t, eq)
// make sure exporterMap after 'Unexport'
fmt.Println(url.Path)
_, ok := proto.(*RestProtocol).ExporterMap().Load(strings.TrimPrefix(url.Path, "/"))
assert.True(t, ok)
var waitGroutp = sync.WaitGroup{}
waitGroutp.Add(1)
waitGroutp.Wait()
exporter.Unexport()
_, ok = proto.(*RestProtocol).ExporterMap().Load(strings.TrimPrefix(url.Path, "/"))
assert.False(t, ok)
// make sure serverMap after 'Destroy'
_, ok = proto.(*RestProtocol).serverMap[url.Location]
assert.True(t, ok)
proto.Destroy()
_, ok = proto.(*RestProtocol).serverMap[url.Location]
assert.False(t, ok)
}
type UserProvider struct {
}
func (p *UserProvider) Reference() string {
return "com.ikurento.user.UserProvider"
}
func (p *UserProvider) GetUser(ctx context.Context, id int, age int32, name string) (*User, error) {
return &User{
Id: id,
Time: time.Now(),
Age: age,
Name: name,
}, nil
}
type User struct {
Id int
Time time.Time
Age int32
Name string
}
package rest_server
import (
"context"
"github.com/apache/dubbo-go/common"
"github.com/apache/dubbo-go/common/logger"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
type GinServer struct {
engine *gin.Engine
srv *http.Server
}
func (gs *GinServer) Start(url common.URL) {
srv := &http.Server{
Addr: ":8080",
Handler: gs.engine,
}
srv.ListenAndServe()
}
func (gs *GinServer) Deploy(service interface{}, config interface{}) {
//TODO gin http 部署接口
// handler中处理:
// http server收到http request,转交给server;
// server根据RestService metadata找到Service,并将请求反序列化,构造好Service调用的上下文;
// RequestMapper这时候将不会处理什么事情,而是直接转交给Service的实现。
//
// gs.engine.Handle(method, relativePath, handler)
}
func (gs *GinServer) Undeploy(url common.URL) {
//TODO gin http 删除接口
}
func (gs *GinServer) Destroy() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := gs.srv.Shutdown(ctx); err != nil {
logger.Errorf("Server Shutdown:", err)
}
logger.Errorf("Server exiting")
}
package rest_server
import (
"context"
"fmt"
"net"
"net/http"
"reflect"
"strconv"
"strings"
"time"
)
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/common/logger"
"github.com/apache/dubbo-go/protocol"
"github.com/apache/dubbo-go/protocol/invocation"
"github.com/apache/dubbo-go/protocol/rest/rest_interface"
"github.com/emicklei/go-restful/v3"
perrors "github.com/pkg/errors"
)
func init() {
extension.SetRestServer(constant.DEFAULT_REST_SERVER, GetNewGoRestfulServer)
}
type GoRestfulServer struct {
srv *http.Server
container *restful.Container
}
func NewGoRestfulServer() *GoRestfulServer {
return &GoRestfulServer{}
}
func (grs *GoRestfulServer) Start(url common.URL) {
grs.container = restful.NewContainer()
grs.srv = &http.Server{
Addr: url.Location,
Handler: grs.container,
}
ln, err := net.Listen("tcp", url.Location)
if err != nil {
panic(perrors.New(fmt.Sprintf("Restful Server start error:%v", err)))
}
go grs.srv.Serve(ln)
}
func (grs *GoRestfulServer) Deploy(invoker protocol.Invoker, restMethodConfig map[string]*rest_interface.RestMethodConfig) {
svc := common.ServiceMap.GetService(invoker.GetUrl().Protocol, strings.TrimPrefix(invoker.GetUrl().Path, "/"))
for methodName, config := range restMethodConfig {
// get method
method := svc.Method()[methodName]
types := method.ArgsType()
f := func(req *restful.Request, resp *restful.Response) {
var (
err error
args []interface{}
)
args = getArgsFromRequest(req, types, config)
result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodName, args, make(map[string]string, 0)))
if result.Error() != nil {
err = resp.WriteError(http.StatusInternalServerError, result.Error())
if err != nil {
logger.Errorf("[Go Restful] WriteError error:%v", err)
}
return
}
err = resp.WriteEntity(result.Result())
if err != nil {
logger.Error("[Go Restful] WriteEntity error:%v", err)
}
}
ws := new(restful.WebService)
ws.Path(config.Path).
Produces(config.Produces).
Consumes(config.Consumes).
Route(ws.Method(config.MethodType).To(f))
grs.container.Add(ws)
}
}
func (grs *GoRestfulServer) UnDeploy(restMethodConfig map[string]*rest_interface.RestMethodConfig) {
for _, config := range restMethodConfig {
ws := new(restful.WebService)
ws.Path(config.Path)
err := grs.container.Remove(ws)
if err != nil {
logger.Warnf("[Go restful] Remove web service error:%v", err)
}
}
}
func (grs *GoRestfulServer) Destroy() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := grs.srv.Shutdown(ctx); err != nil {
logger.Errorf("Server Shutdown:", err)
}
logger.Errorf("Server exiting")
}
func getArgsFromRequest(req *restful.Request, types []reflect.Type, config *rest_interface.RestMethodConfig) []interface{} {
args := make([]interface{}, len(types))
for i, t := range types {
args[i] = reflect.Zero(t).Interface()
}
var (
err error
param interface{}
i64 int64
)
for k, v := range config.PathParamsMap {
if k < 0 || k >= len(types) {
logger.Errorf("[Go restful] Path param parse error, the args:%v doesn't exist", k)
continue
}
t := types[k]
kind := t.Kind()
if kind == reflect.Ptr {
t = t.Elem()
}
if kind == reflect.Int {
param, err = strconv.Atoi(req.PathParameter(v))
} else if kind == reflect.Int32 {
i64, err = strconv.ParseInt(req.PathParameter(v), 10, 32)
if err == nil {
param = int32(i64)
}
} else if kind == reflect.Int64 {
param, err = strconv.ParseInt(req.PathParameter(v), 10, 64)
} else if kind != reflect.String {
logger.Warnf("[Go restful] Path param parse error, the args:%v of type isn't int or string", k)
continue
}
if err != nil {
logger.Errorf("[Go restful] Path param parse error, error is %v", err)
continue
}
args[k] = param
}
for k, v := range config.QueryParamsMap {
if k < 0 || k > len(types) {
logger.Errorf("[Go restful] Query param parse error, the args:%v doesn't exist", k)
continue
}
t := types[k]
kind := t.Kind()
if kind == reflect.Ptr {
t = t.Elem()
}
if kind == reflect.Slice {
param = req.QueryParameters(v)
} else if kind == reflect.String {
param = req.QueryParameter(v)
} else if kind == reflect.Int {
param, err = strconv.Atoi(req.QueryParameter(v))
} else if kind == reflect.Int32 {
i64, err = strconv.ParseInt(req.QueryParameter(v), 10, 32)
if err == nil {
param = int32(i64)
}
} else if kind == reflect.Int64 {
param, err = strconv.ParseInt(req.QueryParameter(v), 10, 64)
} else {
logger.Errorf("[Go restful] Query param parse error, the args:%v of type isn't int or string or slice", k)
continue
}
if err != nil {
logger.Errorf("[Go restful] Query param parse error, error is %v", err)
continue
}
args[k] = param
}
if config.Body > 0 && config.Body < len(types) {
t := types[config.Body]
err := req.ReadEntity(reflect.New(t))
if err != nil {
logger.Errorf("[Go restful] Read body entity error:%v", err)
}
}
for k, v := range config.HeadersMap {
param := req.HeaderParameter(v)
if k < 0 || k >= len(types) {
logger.Errorf("[Go restful] Header param parse error, the args:%v doesn't exist", k)
continue
}
t := types[k]
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() == reflect.String {
args[k] = param
} else {
logger.Errorf("[Go restful] Header param parse error, the args:%v of type isn't string", k)
}
}
return args
}
func GetNewGoRestfulServer() rest_interface.RestServer {
return NewGoRestfulServer()
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment