Added support for new RPC support using HTTP POST. RPC's arguments and reply are Gob encoded and sent as HTTP request/response body. This patch also removes Go RPC based implementation.
265 lines
6.5 KiB
Go
265 lines
6.5 KiB
Go
/*
|
|
* Minio Cloud Storage, (C) 2018 Minio, Inc.
|
|
*
|
|
* Licensed 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 rpc
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/gob"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"reflect"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// Authenticator - validator of first argument of any RPC call.
|
|
type Authenticator interface {
|
|
// Method to validate first argument of any RPC call.
|
|
Authenticate() error
|
|
}
|
|
|
|
// reflect.Type of error interface.
|
|
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
|
|
|
// reflect.Type of Authenticator interface.
|
|
var authenticatorType = reflect.TypeOf((*Authenticator)(nil)).Elem()
|
|
|
|
func gobEncode(e interface{}) ([]byte, error) {
|
|
var buf bytes.Buffer
|
|
|
|
if err := gob.NewEncoder(&buf).Encode(e); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func gobDecode(data []byte, e interface{}) error {
|
|
return gob.NewDecoder(bytes.NewReader(data)).Decode(e)
|
|
}
|
|
|
|
// Returns whether given type is exported or builin type or not.
|
|
func isExportedOrBuiltinType(t reflect.Type) bool {
|
|
for t.Kind() == reflect.Ptr {
|
|
t = t.Elem()
|
|
}
|
|
|
|
rune, _ := utf8.DecodeRuneInString(t.Name())
|
|
return unicode.IsUpper(rune) || t.PkgPath() == ""
|
|
}
|
|
|
|
// Makes method name map from given type.
|
|
func getMethodMap(receiverType reflect.Type) map[string]reflect.Method {
|
|
methodMap := make(map[string]reflect.Method)
|
|
for i := 0; i < receiverType.NumMethod(); i++ {
|
|
// Method.PkgPath is empty for this package.
|
|
method := receiverType.Method(i)
|
|
|
|
// Methods must have three arguments (receiver, args, reply)
|
|
if method.Type.NumIn() != 3 {
|
|
continue
|
|
}
|
|
|
|
// First argument must be exported.
|
|
if !isExportedOrBuiltinType(method.Type.In(1)) {
|
|
continue
|
|
}
|
|
|
|
// First argument must be Authenticator.
|
|
if !method.Type.In(1).Implements(authenticatorType) {
|
|
continue
|
|
}
|
|
|
|
// Second argument must be exported or builtin type.
|
|
if !isExportedOrBuiltinType(method.Type.In(2)) {
|
|
continue
|
|
}
|
|
|
|
// Second argument must be a pointer.
|
|
if method.Type.In(2).Kind() != reflect.Ptr {
|
|
continue
|
|
}
|
|
|
|
// Method must return one value.
|
|
if method.Type.NumOut() != 1 {
|
|
continue
|
|
}
|
|
|
|
// The return type of the method must be error.
|
|
if method.Type.Out(0) != errorType {
|
|
continue
|
|
}
|
|
|
|
methodMap[method.Name] = method
|
|
}
|
|
|
|
return methodMap
|
|
}
|
|
|
|
// Server - HTTP based RPC server.
|
|
type Server struct {
|
|
serviceName string
|
|
receiverValue reflect.Value
|
|
methodMap map[string]reflect.Method
|
|
}
|
|
|
|
// RegisterName - registers receiver with given name to handle RPC requests.
|
|
func (server *Server) RegisterName(name string, receiver interface{}) error {
|
|
server.serviceName = name
|
|
|
|
server.receiverValue = reflect.ValueOf(receiver)
|
|
if !reflect.Indirect(server.receiverValue).IsValid() {
|
|
return fmt.Errorf("nil receiver")
|
|
}
|
|
|
|
receiverName := reflect.Indirect(server.receiverValue).Type().Name()
|
|
receiverType := reflect.TypeOf(receiver)
|
|
server.methodMap = getMethodMap(receiverType)
|
|
if len(server.methodMap) == 0 {
|
|
str := "rpc.Register: type " + receiverName + " has no exported methods of suitable type"
|
|
|
|
// To help the user, see if a pointer receiver would work.
|
|
if len(getMethodMap(reflect.PtrTo(receiverType))) != 0 {
|
|
str += " (hint: pass a pointer to value of that type)"
|
|
}
|
|
|
|
return errors.New(str)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// call - call service method in receiver.
|
|
func (server *Server) call(serviceMethod string, argBytes []byte) (replyBytes []byte, err error) {
|
|
tokens := strings.SplitN(serviceMethod, ".", 2)
|
|
if len(tokens) != 2 {
|
|
return nil, fmt.Errorf("invalid service/method request ill-formed %v", serviceMethod)
|
|
}
|
|
|
|
serviceName := tokens[0]
|
|
if serviceName != server.serviceName {
|
|
return nil, fmt.Errorf("can't find service %v", serviceName)
|
|
}
|
|
|
|
methodName := tokens[1]
|
|
method, found := server.methodMap[methodName]
|
|
if !found {
|
|
return nil, fmt.Errorf("can't find method %v", methodName)
|
|
}
|
|
|
|
var argv reflect.Value
|
|
|
|
// Decode the argument value.
|
|
argIsValue := false // if true, need to indirect before calling.
|
|
if method.Type.In(1).Kind() == reflect.Ptr {
|
|
argv = reflect.New(method.Type.In(1).Elem())
|
|
} else {
|
|
argv = reflect.New(method.Type.In(1))
|
|
argIsValue = true
|
|
}
|
|
|
|
if err = gobDecode(argBytes, argv.Interface()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if argIsValue {
|
|
argv = argv.Elem()
|
|
}
|
|
|
|
// call Authenticate() method.
|
|
authMethod, ok := method.Type.In(1).MethodByName("Authenticate")
|
|
if !ok {
|
|
panic("Authenticate() method not found. This should not happen.")
|
|
}
|
|
returnValues := authMethod.Func.Call([]reflect.Value{argv})
|
|
errInter := returnValues[0].Interface()
|
|
if errInter != nil {
|
|
err = errInter.(error)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
replyv := reflect.New(method.Type.In(2).Elem())
|
|
|
|
switch method.Type.In(2).Elem().Kind() {
|
|
case reflect.Map:
|
|
replyv.Elem().Set(reflect.MakeMap(method.Type.In(2).Elem()))
|
|
case reflect.Slice:
|
|
replyv.Elem().Set(reflect.MakeSlice(method.Type.In(2).Elem(), 0, 0))
|
|
}
|
|
|
|
returnValues = method.Func.Call([]reflect.Value{server.receiverValue, argv, replyv})
|
|
errInter = returnValues[0].Interface()
|
|
if errInter != nil {
|
|
err = errInter.(error)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return gobEncode(replyv.Interface())
|
|
}
|
|
|
|
// CallRequest - RPC call request parameters.
|
|
type CallRequest struct {
|
|
Method string
|
|
ArgBytes []byte
|
|
}
|
|
|
|
// CallResponse - RPC call response parameters.
|
|
type CallResponse struct {
|
|
Error string
|
|
ReplyBytes []byte
|
|
}
|
|
|
|
// ServeHTTP - handles RPC on HTTP request.
|
|
func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
if req.Method != http.MethodPost {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
var callRequest CallRequest
|
|
if err := gob.NewDecoder(req.Body).Decode(&callRequest); err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var callResponse CallResponse
|
|
var err error
|
|
callResponse.ReplyBytes, err = server.call(callRequest.Method, callRequest.ArgBytes)
|
|
if err != nil {
|
|
callResponse.Error = err.Error()
|
|
}
|
|
|
|
data, err := gobEncode(callResponse)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Write(data)
|
|
}
|
|
|
|
// NewServer - returns new RPC server.
|
|
func NewServer() *Server {
|
|
return &Server{}
|
|
}
|