RPC Client / tests
License: MIT Signed-off-by: Jakub Sztandera <kubuxu@protonmail.ch>
This commit is contained in:
parent
3375a72aea
commit
b1cea1b3ca
@ -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
119
rpclib/rpcClient.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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
145
rpclib/rpc_test.go
Normal 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()
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user