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)
|
||||
|
||||
rpcServer := rpclib.NewServer()
|
||||
|
||||
rpcServer.Register(fc)
|
||||
|
||||
http.Handle("/rpc/v0", rpcServer)
|
||||
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"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@ -31,7 +30,9 @@ func NewServer() *RPCServer {
|
||||
}
|
||||
|
||||
type param struct {
|
||||
data []byte
|
||||
data []byte // from unmarshal
|
||||
|
||||
v reflect.Value // to marshal
|
||||
}
|
||||
|
||||
func (p *param) UnmarshalJSON(raw []byte) error {
|
||||
@ -40,9 +41,13 @@ func (p *param) UnmarshalJSON(raw []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *param) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(p.v.Interface())
|
||||
}
|
||||
|
||||
type request struct {
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
Id *int `json:"id,omitempty"`
|
||||
Id *int64 `json:"id,omitempty"`
|
||||
Method string `json:"method"`
|
||||
Params []param `json:"params"`
|
||||
}
|
||||
@ -54,8 +59,8 @@ type respError struct {
|
||||
|
||||
type response struct {
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
Result interface{} `json:"result"`
|
||||
Id int `json:"id"`
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
Id int64 `json:"id"`
|
||||
Error *respError `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
@ -72,6 +77,11 @@ func (s *RPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Params) != handler.nParams {
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
callParams := make([]reflect.Value, 1+handler.nParams)
|
||||
callParams[0] = handler.receiver
|
||||
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()
|
||||
}
|
||||
|
||||
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{}) {
|
||||
@ -129,26 +142,7 @@ func (s *RPCServer) Register(r interface{}) {
|
||||
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")
|
||||
}
|
||||
valOut, errOut, _ := processFuncOut(funcType)
|
||||
|
||||
s.methods[name+"."+method.Name] = rpcHandler{
|
||||
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