From 3375a72aea6f2316a585c2c0bba18b74caf73fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 28 Jun 2019 11:03:28 +0200 Subject: [PATCH] RPC server License: MIT Signed-off-by: Jakub Sztandera --- cli/cmd.go | 2 +- cli/version.go | 2 +- cmd/lotus/main.go | 4 +- daemon/builder.go | 2 - daemon/cmd.go | 2 +- daemon/modules/testing.go | 2 - daemon/rpc.go | 22 +++-- rpclib/rpc.go | 164 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 178 insertions(+), 22 deletions(-) create mode 100644 rpclib/rpc.go diff --git a/cli/cmd.go b/cli/cmd.go index e9bb9133b..f701a4401 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -6,4 +6,4 @@ import ( var Commands = []*cli.Command{ versionCmd, -} \ No newline at end of file +} diff --git a/cli/version.go b/cli/version.go index cb3f3c0c8..57d101d83 100644 --- a/cli/version.go +++ b/cli/version.go @@ -5,7 +5,7 @@ import ( ) var versionCmd = &cli.Command{ - Name: "version", + Name: "version", Usage: "Print version", Action: func(context *cli.Context) error { // TODO: print more useful things diff --git a/cmd/lotus/main.go b/cmd/lotus/main.go index b8df0350f..c8e168c11 100644 --- a/cmd/lotus/main.go +++ b/cmd/lotus/main.go @@ -17,8 +17,8 @@ func main() { } app := &cli.App{ - Name: "lotus", - Usage: "Filecoin decentralized storage network client", + Name: "lotus", + Usage: "Filecoin decentralized storage network client", Version: build.Version, Commands: append(local, lcli.Commands...), diff --git a/daemon/builder.go b/daemon/builder.go index 46fa88c82..9d3072094 100644 --- a/daemon/builder.go +++ b/daemon/builder.go @@ -1,3 +1 @@ package daemon - - diff --git a/daemon/cmd.go b/daemon/cmd.go index 99c7907a1..aa62f8312 100644 --- a/daemon/cmd.go +++ b/daemon/cmd.go @@ -5,7 +5,7 @@ import ( ) var Cmd = &cli.Command{ - Name: "daemon", + Name: "daemon", Usage: "Start a lotus daemon process", Action: func(context *cli.Context) error { return serveRPC() diff --git a/daemon/modules/testing.go b/daemon/modules/testing.go index 324391f6d..11174d7d4 100644 --- a/daemon/modules/testing.go +++ b/daemon/modules/testing.go @@ -1,3 +1 @@ package modules - - diff --git a/daemon/rpc.go b/daemon/rpc.go index c666f884d..3dfdbb749 100644 --- a/daemon/rpc.go +++ b/daemon/rpc.go @@ -1,31 +1,27 @@ package daemon import ( + "fmt" "net/http" - "github.com/gorilla/rpc/v2" - "github.com/gorilla/rpc/v2/json" - "github.com/filecoin-project/go-lotus/build" + "github.com/filecoin-project/go-lotus/rpclib" ) -type Filecoin struct {} +type Filecoin struct{} -func (*Filecoin) ServerVersion(r *http.Request, _ *struct{}, out *string) error { - *out = build.Version - return nil +func (*Filecoin) ServerVersion(in int) (string, error) { + fmt.Println(in) + return build.Version, nil } func serveRPC() error { fc := new(Filecoin) - rpcServer := rpc.NewServer() - rpcServer.RegisterCodec(json.NewCodec(), "application/json") - if err := rpcServer.RegisterService(fc, ""); err != nil { - return err - } + rpcServer := rpclib.NewServer() + + rpcServer.Register(fc) http.Handle("/rpc/v0", rpcServer) return http.ListenAndServe(":1234", http.DefaultServeMux) } - diff --git a/rpclib/rpc.go b/rpclib/rpc.go new file mode 100644 index 000000000..c55ec8386 --- /dev/null +++ b/rpclib/rpc.go @@ -0,0 +1,164 @@ +package rpclib + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "os" + "reflect" +) + +type rpcHandler struct { + paramReceivers []reflect.Type + nParams int + + receiver reflect.Value + handlerFunc reflect.Value + + errOut int + valOut int +} + +type RPCServer struct { + methods map[string]rpcHandler +} + +func NewServer() *RPCServer { + return &RPCServer{ + methods: map[string]rpcHandler{}, + } +} + +type param struct { + data []byte +} + +func (p *param) UnmarshalJSON(raw []byte) error { + p.data = make([]byte, len(raw)) + copy(p.data, raw) + return nil +} + +type request struct { + Jsonrpc string `json:"jsonrpc"` + Id *int `json:"id,omitempty"` + Method string `json:"method"` + Params []param `json:"params"` +} + +type respError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type response struct { + Jsonrpc string `json:"jsonrpc"` + Result interface{} `json:"result"` + Id int `json:"id"` + Error *respError `json:"error,omitempty"` +} + +func (s *RPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var req request + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + w.WriteHeader(500) + return + } + + handler, ok := s.methods[req.Method] + if !ok { + w.WriteHeader(500) + return + } + + callParams := make([]reflect.Value, 1+handler.nParams) + callParams[0] = handler.receiver + for i := 0; i < handler.nParams; i++ { + rp := reflect.New(handler.paramReceivers[i]) + if err := json.NewDecoder(bytes.NewReader(req.Params[i].data)).Decode(rp.Interface()); err != nil { + w.WriteHeader(500) + fmt.Println(err) + return + } + + callParams[i+1] = reflect.ValueOf(rp.Elem().Interface()) + } + + callResult := handler.handlerFunc.Call(callParams) + if req.Id == nil { + return // notification + } + + resp := response{ + Jsonrpc: "2.0", + Id: *req.Id, + } + + if handler.errOut != -1 { + err := callResult[handler.errOut].Interface() + if err != nil { + resp.Error = &respError{ + Code: 1, + Message: err.(error).Error(), + } + } + } + if handler.valOut != -1 { + resp.Result = callResult[handler.valOut].Interface() + } + + json.NewEncoder(os.Stderr).Encode(resp) +} + +func (s *RPCServer) Register(r interface{}) { + val := reflect.ValueOf(r) + //TODO: expect ptr + + name := val.Type().Elem().Name() + + for i := 0; i < val.NumMethod(); i++ { + method := val.Type().Method(i) + + fmt.Println(name + "." + method.Name) + + funcType := method.Func.Type() + ins := funcType.NumIn() - 1 + recvs := make([]reflect.Type, ins) + for i := 0; i < ins; i++ { + recvs[i] = method.Type.In(i + 1) + } + + errOut := -1 + valOut := -1 + + switch funcType.NumOut() { + case 0: + case 1: + if funcType.Out(0) == reflect.TypeOf(new(error)).Elem() { + errOut = 0 + } else { + valOut = 0 + } + case 2: + valOut = 0 + errOut = 1 + if funcType.Out(1) != reflect.TypeOf(new(error)).Elem() { + panic("expected error as second return value") + } + default: + panic("too many error values") + } + + s.methods[name+"."+method.Name] = rpcHandler{ + paramReceivers: recvs, + nParams: ins, + + handlerFunc: method.Func, + receiver: val, + + errOut: errOut, + valOut: valOut, + } + } +}