RPC Client / tests

License: MIT
Signed-off-by: Jakub Sztandera <kubuxu@protonmail.ch>
This commit is contained in:
Łukasz Magiera 2019-06-28 15:49:34 +02:00 committed by Jakub Sztandera
parent 3375a72aea
commit b1cea1b3ca
4 changed files with 310 additions and 28 deletions

View File

@ -19,9 +19,7 @@ func serveRPC() error {
fc := new(Filecoin) fc := new(Filecoin)
rpcServer := rpclib.NewServer() rpcServer := rpclib.NewServer()
rpcServer.Register(fc) rpcServer.Register(fc)
http.Handle("/rpc/v0", rpcServer) http.Handle("/rpc/v0", rpcServer)
return http.ListenAndServe(":1234", http.DefaultServeMux) return http.ListenAndServe(":1234", http.DefaultServeMux)
} }

119
rpclib/rpcClient.go Normal file
View File

@ -0,0 +1,119 @@
package rpclib
import (
"bytes"
"encoding/json"
"errors"
"net/http"
"reflect"
"sync/atomic"
)
type result reflect.Value
func (r *result) UnmarshalJSON(raw []byte) error {
return json.Unmarshal(raw, reflect.Value(*r).Interface())
}
type clientResponse struct {
Jsonrpc string `json:"jsonrpc"`
Result result `json:"result"`
Id int64 `json:"id"`
Error *respError `json:"error,omitempty"`
}
func NewClient(addr string, namespace string, handler interface{}) {
htyp := reflect.TypeOf(handler)
if htyp.Kind() != reflect.Ptr {
panic("expected handler to be a pointer")
}
typ := htyp.Elem()
if typ.Kind() != reflect.Struct {
panic("handler should be a struct")
}
val := reflect.ValueOf(handler)
var idCtr int64
for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i)
ftyp := f.Type
if ftyp.Kind() != reflect.Func {
panic("handler field not a func")
}
valOut, errOut, nout := processFuncOut(ftyp)
processResponse := func(resp clientResponse) []reflect.Value {
out := make([]reflect.Value, nout)
if valOut != -1 {
out[valOut] = reflect.Value(resp.Result).Elem()
}
if errOut != -1 {
out[errOut] = reflect.New(reflect.TypeOf(new(error)).Elem()).Elem()
if resp.Error != nil {
out[errOut].Set(reflect.ValueOf(errors.New(resp.Error.Message)))
}
}
return out
}
fn := reflect.MakeFunc(ftyp, func(args []reflect.Value) (results []reflect.Value) {
id := atomic.AddInt64(&idCtr, 1)
params := make([]param, len(args))
for i, arg := range args {
params[i] = param{
v: arg,
}
}
req := request{
Jsonrpc: "2.0",
Id: &id,
Method: namespace + "." + f.Name,
Params: params,
}
b, err := json.Marshal(&req)
if err != nil {
// TODO: try returning an error if the method has one
panic(err)
}
httpResp, err := http.Post(addr, "application/json", bytes.NewReader(b))
if err != nil {
// TODO: try returning an error if the method has one
panic(err)
}
defer httpResp.Body.Close()
if httpResp.StatusCode != 200 {
// TODO: try returning an error if the method has one
// TODO: actually parse response, it haz right errors
panic("non 200 code")
}
var resp clientResponse
if valOut != -1 {
resp.Result = result(reflect.New(ftyp.Out(valOut)))
}
if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil {
// TODO: try returning an error if the method has one
panic(err)
}
if resp.Id != *req.Id {
// TODO: try returning an error if the method has one
panic("request and response id didn't match")
}
return processResponse(resp)
})
val.Elem().Field(i).Set(fn)
}
}

View File

@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"os"
"reflect" "reflect"
) )
@ -31,7 +30,9 @@ func NewServer() *RPCServer {
} }
type param struct { type param struct {
data []byte data []byte // from unmarshal
v reflect.Value // to marshal
} }
func (p *param) UnmarshalJSON(raw []byte) error { func (p *param) UnmarshalJSON(raw []byte) error {
@ -40,9 +41,13 @@ func (p *param) UnmarshalJSON(raw []byte) error {
return nil return nil
} }
func (p *param) MarshalJSON() ([]byte, error) {
return json.Marshal(p.v.Interface())
}
type request struct { type request struct {
Jsonrpc string `json:"jsonrpc"` Jsonrpc string `json:"jsonrpc"`
Id *int `json:"id,omitempty"` Id *int64 `json:"id,omitempty"`
Method string `json:"method"` Method string `json:"method"`
Params []param `json:"params"` Params []param `json:"params"`
} }
@ -54,8 +59,8 @@ type respError struct {
type response struct { type response struct {
Jsonrpc string `json:"jsonrpc"` Jsonrpc string `json:"jsonrpc"`
Result interface{} `json:"result"` Result interface{} `json:"result,omitempty"`
Id int `json:"id"` Id int64 `json:"id"`
Error *respError `json:"error,omitempty"` Error *respError `json:"error,omitempty"`
} }
@ -72,6 +77,11 @@ func (s *RPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
if len(req.Params) != handler.nParams {
w.WriteHeader(500)
return
}
callParams := make([]reflect.Value, 1+handler.nParams) callParams := make([]reflect.Value, 1+handler.nParams)
callParams[0] = handler.receiver callParams[0] = handler.receiver
for i := 0; i < handler.nParams; i++ { for i := 0; i < handler.nParams; i++ {
@ -108,7 +118,10 @@ func (s *RPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
resp.Result = callResult[handler.valOut].Interface() resp.Result = callResult[handler.valOut].Interface()
} }
json.NewEncoder(os.Stderr).Encode(resp) if err := json.NewEncoder(w).Encode(resp); err != nil {
w.WriteHeader(500)
return
}
} }
func (s *RPCServer) Register(r interface{}) { func (s *RPCServer) Register(r interface{}) {
@ -129,26 +142,7 @@ func (s *RPCServer) Register(r interface{}) {
recvs[i] = method.Type.In(i + 1) recvs[i] = method.Type.In(i + 1)
} }
errOut := -1 valOut, errOut, _ := processFuncOut(funcType)
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{ s.methods[name+"."+method.Name] = rpcHandler{
paramReceivers: recvs, paramReceivers: recvs,
@ -162,3 +156,29 @@ func (s *RPCServer) Register(r interface{}) {
} }
} }
} }
func processFuncOut(funcType reflect.Type) (valOut int, errOut int, n int) {
errOut = -1
valOut = -1
n = funcType.NumOut()
switch n {
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")
}
return
}

145
rpclib/rpc_test.go Normal file
View File

@ -0,0 +1,145 @@
package rpclib
import (
"errors"
"net/http/httptest"
"strconv"
"testing"
)
type SimpleServerHandler struct {
n int
}
type TestType struct {
S string
I int
}
type TestOut struct {
TestType
Ok bool
}
func (h *SimpleServerHandler) Add(in int) error {
if in == -3546 {
return errors.New("test")
}
h.n += in
return nil
}
func (h *SimpleServerHandler) AddGet(in int) int {
h.n += in
return h.n
}
func (h *SimpleServerHandler) StringMatch(t TestType, i2 int64) (out TestOut, err error) {
if strconv.FormatInt(i2, 10) == t.S {
out.Ok = true
}
if i2 != int64(t.I) {
return TestOut{}, errors.New(":(")
}
out.I = t.I
out.S = t.S
return
}
func TestRPC(t *testing.T) {
// setup server
serverHandler := &SimpleServerHandler{}
rpcServer := NewServer()
rpcServer.Register(serverHandler)
// httptest stuff
testServ := httptest.NewServer(rpcServer)
defer testServ.Close()
// setup client
var client struct {
Add func(int) error
AddGet func(int) int
StringMatch func(t TestType, i2 int64) (out TestOut, err error)
}
NewClient(testServ.URL, "SimpleServerHandler", &client)
// Add(int) error
if err := client.Add(2); err != nil {
t.Fatal(err)
}
if serverHandler.n != 2 {
t.Error("expected 2")
}
err := client.Add(-3546)
if err == nil {
t.Fatal("expected error")
}
if err.Error() != "test" {
t.Fatal("wrong error")
}
// AddGet(int) int
n := client.AddGet(3)
if n != 5 {
t.Error("wrong n")
}
if serverHandler.n != 5 {
t.Error("expected 5")
}
// StringMatch
o, err := client.StringMatch(TestType{S: "0"}, 0)
if err != nil {
t.Error(err)
}
if o.S != "0" || o.I != 0 {
t.Error("wrong result")
}
_, err = client.StringMatch(TestType{S: "5"}, 5)
if err == nil || err.Error() != ":(" {
t.Error("wrong err")
}
o, err = client.StringMatch(TestType{S: "8", I: 8}, 8)
if err != nil {
t.Error(err)
}
if o.S != "8" || o.I != 8 {
t.Error("wrong result")
}
// Invalid client handlers
var noret struct {
Add func(int)
}
NewClient(testServ.URL, "SimpleServerHandler", &noret)
// this one should actually work
noret.Add(4)
if serverHandler.n != 9 {
t.Error("expected 9")
}
var noparam struct {
Add func()
}
NewClient(testServ.URL, "SimpleServerHandler", &noparam)
// shouldn't panic
noparam.Add()
}