blob: d9542bb8760cd2ddb7b839ebc6761dab75ae9c30 [file] [log] [blame]
/*
* 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 server
import (
"context"
"errors"
"net/http"
"reflect"
"strconv"
"strings"
)
import (
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"
"github.com/apache/dubbo-go/protocol/invocation"
rest_config "github.com/apache/dubbo-go/protocol/rest/config"
)
const parseParameterErrorStr = "An error occurred while parsing parameters on the server"
// RestServer user can implement this server interface
type RestServer interface {
// Start rest server
Start(url common.URL)
// Deploy a http api
Deploy(restMethodConfig *rest_config.RestMethodConfig, routeFunc func(request RestServerRequest, response RestServerResponse))
// UnDeploy a http api
UnDeploy(restMethodConfig *rest_config.RestMethodConfig)
// Destroy rest server
Destroy()
}
// RestServerRequest interface
type RestServerRequest interface {
// RawRequest get the Ptr of http.Request
RawRequest() *http.Request
// PathParameter get the path parameter by name
PathParameter(name string) string
// PathParameters get the map of the path parameters
PathParameters() map[string]string
// QueryParameter get the query parameter by name
QueryParameter(name string) string
// QueryParameters get the map of query parameters
QueryParameters(name string) []string
// BodyParameter get the body parameter of name
BodyParameter(name string) (string, error)
// HeaderParameter get the header parameter of name
HeaderParameter(name string) string
// ReadEntity checks the Accept header and reads the content into the entityPointer.
ReadEntity(entityPointer interface{}) error
}
// RestServerResponse interface
type RestServerResponse interface {
http.ResponseWriter
// WriteError writes the http status and the error string on the response. err can be nil.
// Return an error if writing was not successful.
WriteError(httpStatus int, err error) (writeErr error)
// WriteEntity marshals the value using the representation denoted by the Accept Header.
WriteEntity(value interface{}) error
}
// GetRouteFunc
// A route function will be invoked by http server
func GetRouteFunc(invoker protocol.Invoker, methodConfig *rest_config.RestMethodConfig) func(req RestServerRequest, resp RestServerResponse) {
return func(req RestServerRequest, resp RestServerResponse) {
var (
err error
args []interface{}
)
svc := common.ServiceMap.GetService(invoker.GetUrl().Protocol, strings.TrimPrefix(invoker.GetUrl().Path, "/"))
// get method
method := svc.Method()[methodConfig.MethodName]
argsTypes := method.ArgsType()
replyType := method.ReplyType()
// two ways to prepare arguments
// if method like this 'func1(req []interface{}, rsp *User) error'
// we don't have arguments type
if (len(argsTypes) == 1 || len(argsTypes) == 2 && replyType == nil) &&
argsTypes[0].String() == "[]interface {}" {
args, err = getArgsInterfaceFromRequest(req, methodConfig)
} else {
args, err = getArgsFromRequest(req, argsTypes, methodConfig)
}
if err != nil {
logger.Errorf("[Go Restful] parsing http parameters error:%v", err)
err = resp.WriteError(http.StatusInternalServerError, errors.New(parseParameterErrorStr))
if err != nil {
logger.Errorf("[Go Restful] WriteErrorString error:%v", err)
}
}
result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodConfig.MethodName, args, make(map[string]interface{})))
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.Errorf("[Go Restful] WriteEntity error:%v", err)
}
}
}
// getArgsInterfaceFromRequest when service function like GetUser(req []interface{}, rsp *User) error
// use this method to get arguments
func getArgsInterfaceFromRequest(req RestServerRequest, methodConfig *rest_config.RestMethodConfig) ([]interface{}, error) {
argsMap := make(map[int]interface{}, 8)
maxKey := 0
for k, v := range methodConfig.PathParamsMap {
if maxKey < k {
maxKey = k
}
argsMap[k] = req.PathParameter(v)
}
for k, v := range methodConfig.QueryParamsMap {
if maxKey < k {
maxKey = k
}
params := req.QueryParameters(v)
if len(params) == 1 {
argsMap[k] = params[0]
} else {
argsMap[k] = params
}
}
for k, v := range methodConfig.HeadersMap {
if maxKey < k {
maxKey = k
}
argsMap[k] = req.HeaderParameter(v)
}
if methodConfig.Body >= 0 {
if maxKey < methodConfig.Body {
maxKey = methodConfig.Body
}
m := make(map[string]interface{})
// TODO read as a slice
if err := req.ReadEntity(&m); err != nil {
return nil, perrors.Errorf("[Go restful] Read body entity as map[string]interface{} error:%v", err)
}
argsMap[methodConfig.Body] = m
}
args := make([]interface{}, maxKey+1)
for k, v := range argsMap {
if k >= 0 {
args[k] = v
}
}
return args, nil
}
// getArgsFromRequest get arguments from server.RestServerRequest
func getArgsFromRequest(req RestServerRequest, argsTypes []reflect.Type, methodConfig *rest_config.RestMethodConfig) ([]interface{}, error) {
argsLength := len(argsTypes)
args := make([]interface{}, argsLength)
for i, t := range argsTypes {
args[i] = reflect.Zero(t).Interface()
}
if err := assembleArgsFromPathParams(methodConfig, argsLength, argsTypes, req, args); err != nil {
return nil, err
}
if err := assembleArgsFromQueryParams(methodConfig, argsLength, argsTypes, req, args); err != nil {
return nil, err
}
if err := assembleArgsFromBody(methodConfig, argsTypes, req, args); err != nil {
return nil, err
}
if err := assembleArgsFromHeaders(methodConfig, req, argsLength, argsTypes, args); err != nil {
return nil, err
}
return args, nil
}
// assembleArgsFromHeaders assemble arguments from headers
func assembleArgsFromHeaders(methodConfig *rest_config.RestMethodConfig, req RestServerRequest, argsLength int, argsTypes []reflect.Type, args []interface{}) error {
for k, v := range methodConfig.HeadersMap {
param := req.HeaderParameter(v)
if k < 0 || k >= argsLength {
return perrors.Errorf("[Go restful] Header param parse error, the index %v args of method:%v doesn't exist", k, methodConfig.MethodName)
}
t := argsTypes[k]
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() == reflect.String {
args[k] = param
} else {
return perrors.Errorf("[Go restful] Header param parse error, the index %v args's type isn't string", k)
}
}
return nil
}
// assembleArgsFromBody assemble arguments from body
func assembleArgsFromBody(methodConfig *rest_config.RestMethodConfig, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) error {
if methodConfig.Body >= 0 && methodConfig.Body < len(argsTypes) {
t := argsTypes[methodConfig.Body]
kind := t.Kind()
if kind == reflect.Ptr {
t = t.Elem()
}
var ni interface{}
if t.String() == "[]interface {}" {
ni = make([]map[string]interface{}, 0)
} else if t.String() == "interface {}" {
ni = make(map[string]interface{})
} else {
n := reflect.New(t)
if n.CanInterface() {
ni = n.Interface()
}
}
if err := req.ReadEntity(&ni); err != nil {
return perrors.Errorf("[Go restful] Read body entity error, error is %v", perrors.WithStack(err))
}
args[methodConfig.Body] = ni
}
return nil
}
// assembleArgsFromQueryParams assemble arguments from query params
func assembleArgsFromQueryParams(methodConfig *rest_config.RestMethodConfig, argsLength int, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) error {
var (
err error
param interface{}
i64 int64
)
for k, v := range methodConfig.QueryParamsMap {
if k < 0 || k >= argsLength {
return perrors.Errorf("[Go restful] Query param parse error, the index %v args of method:%v doesn't exist", k, methodConfig.MethodName)
}
t := argsTypes[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 {
return perrors.Errorf("[Go restful] Query param parse error, the index %v args's type isn't int or string or slice", k)
}
if err != nil {
return perrors.Errorf("[Go restful] Query param parse error, error:%v", perrors.WithStack(err))
}
args[k] = param
}
return nil
}
// assembleArgsFromPathParams assemble arguments from path params
func assembleArgsFromPathParams(methodConfig *rest_config.RestMethodConfig, argsLength int, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) error {
var (
err error
param interface{}
i64 int64
)
for k, v := range methodConfig.PathParamsMap {
if k < 0 || k >= argsLength {
return perrors.Errorf("[Go restful] Path param parse error, the index %v args of method:%v doesn't exist", k, methodConfig.MethodName)
}
t := argsTypes[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 {
param = req.PathParameter(v)
} else {
return perrors.Errorf("[Go restful] Path param parse error, the index %v args's type isn't int or string", k)
}
if err != nil {
return perrors.Errorf("[Go restful] Path param parse error, error is %v", perrors.WithStack(err))
}
args[k] = param
}
return nil
}