mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 05:36:46 +00:00
rpc ethereum client protocol RPC integration
This commit is contained in:
parent
27528ad3d2
commit
98d3b24637
6 changed files with 1106 additions and 0 deletions
75
rpc/ecp/doc.go
Normal file
75
rpc/ecp/doc.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/*
|
||||
Package ecp implements the Ethereum Client Protocol (ECP) and contains a server to
|
||||
create a RPC server offering a public API using ECP.
|
||||
|
||||
The purpose of this package is to provide access to exported methods an I/O connection.
|
||||
A client can instantiate a server and register objects. The server will expose the
|
||||
object as a service. After registration exported methods can be called remotely.
|
||||
|
||||
Methods that satisfy the following criteria will be exposed:
|
||||
- the object is exported
|
||||
- the method is exported
|
||||
- arguments must be builtin types or *big.Int
|
||||
- when the method returns an error its must be the last returned values
|
||||
|
||||
The REdis Serialization Protocol (http://redis.io/topics/protocol) is used as
|
||||
serialization format. It is a fast and easy to parse protocol which is binary safe.
|
||||
Currently the following types are supported:
|
||||
|
||||
- string, send as simple string
|
||||
- []byte, send as bulk/binary string
|
||||
- big.Int, send as bulk/binary string in big endian format
|
||||
- int64, send as integer
|
||||
- error, as the default error type starting with an error code following the error message
|
||||
- struct, fields are send in an array in the definition order
|
||||
- array, as an array
|
||||
|
||||
Nil values are send as a null array in case the type is an array, otherwise as a null
|
||||
binary string.
|
||||
|
||||
See the official REdis Serialization Protocol specification page for a description and
|
||||
examples of how data is serialized. The ecp server uses the same convention as the Redis
|
||||
server for messages. Requests and responses are represented in an array. Services who return
|
||||
no data return with the +OK\r\n message as an indication that the request was processed.
|
||||
|
||||
Example server:
|
||||
|
||||
type ExampleService struct {
|
||||
}
|
||||
|
||||
func (s *ExampleService) Echo(str string, i int) (string, int) {
|
||||
return str, i
|
||||
}
|
||||
|
||||
func main() {
|
||||
srvr := ecp.NewServer()
|
||||
srvr.RegisterName("example", new(ExampleService))
|
||||
|
||||
l, _ := net.ListenUnix("unix", &net.UnixAddr{Name: "/tmp/example.sock", Net: "unix"})
|
||||
go srvr.Serve(l)
|
||||
...
|
||||
srvr.Stop()
|
||||
}
|
||||
|
||||
A client can now send the following request:
|
||||
*3\r\n+example.Echo\r\n+some string\r\n:42\r\n
|
||||
And receive the following response:
|
||||
*2\r\n+some string\r\n:42\r\n
|
||||
*/
|
||||
package ecp
|
||||
119
rpc/ecp/errors.go
Normal file
119
rpc/ecp/errors.go
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
package ecp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type errorCode int
|
||||
|
||||
const (
|
||||
_ errorCode = iota
|
||||
ProtocolError // Received message invalid
|
||||
InvalidRequestError // Received message isn't a valid request
|
||||
MethodNotFoundError // Requested method not found
|
||||
InvalidArguments // Received arguments are invalid
|
||||
LogicError // Service returned an error
|
||||
)
|
||||
|
||||
// isRecoverable returns an indication if the server can recover from the error
|
||||
func isRecoverable(err error) bool {
|
||||
switch err.(type) {
|
||||
case *unknownServiceError, *unknownMethodError, *invalidNumberOfArgumentsError, *invalidStructArgumentError,
|
||||
*invalidArgumentError, *invalidRequestError, *unsupportedTypeError, *callbackError:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// InvalidLeadInError is raised when the received message doesn't for a valid
|
||||
// RESP message. It is a bit more detailed than InvalidRequestError.
|
||||
type invalidLeadInError struct {
|
||||
got byte
|
||||
}
|
||||
|
||||
func (e *invalidLeadInError) Error() string {
|
||||
return fmt.Sprintf("%d ECP error, received unexpected byte 0x%x", ProtocolError, e.got)
|
||||
}
|
||||
|
||||
// UnexpectedByteError occurs when incoming data doesn't follow the protocol specifications
|
||||
type unexpectedByteError struct {
|
||||
expected, got byte
|
||||
}
|
||||
|
||||
func (e *unexpectedByteError) Error() string {
|
||||
return fmt.Sprintf("%d ECP error, expected 0x%x got 0x%x", ProtocolError, e.expected, e.got)
|
||||
}
|
||||
|
||||
type unknownServiceError struct {
|
||||
svc string
|
||||
}
|
||||
|
||||
func (e *unknownServiceError) Error() string {
|
||||
return fmt.Sprintf("%d service '%s' not found", MethodNotFoundError, e.svc)
|
||||
}
|
||||
|
||||
type unknownMethodError struct {
|
||||
svc string
|
||||
method string
|
||||
}
|
||||
|
||||
func (e *unknownMethodError) Error() string {
|
||||
return fmt.Sprintf("%d method '%s.%s' not found", MethodNotFoundError, e.svc, e.method)
|
||||
}
|
||||
|
||||
type invalidNumberOfArgumentsError struct {
|
||||
svc string
|
||||
method string
|
||||
exp int
|
||||
got int
|
||||
}
|
||||
|
||||
func (e *invalidNumberOfArgumentsError) Error() string {
|
||||
return fmt.Sprintf("%d %s.%s expect %d argument(s) but got %d",
|
||||
InvalidArguments, e.svc, e.method, e.exp, e.got)
|
||||
}
|
||||
|
||||
type invalidArgumentError struct {
|
||||
pos int
|
||||
exp reflect.Type
|
||||
got reflect.Type
|
||||
}
|
||||
|
||||
func (e *invalidArgumentError) Error() string {
|
||||
return fmt.Sprintf("%d expected type for argument %d is %s but got %s", InvalidArguments, e.pos, e.exp, e.got)
|
||||
}
|
||||
|
||||
type invalidStructArgumentError struct {
|
||||
to reflect.Type
|
||||
exp int
|
||||
got int
|
||||
}
|
||||
|
||||
func (e *invalidStructArgumentError) Error() string {
|
||||
return fmt.Sprintf("%s expected %d values but got %d", e.to.Name(), e.exp, e.got)
|
||||
}
|
||||
|
||||
type invalidRequestError struct {
|
||||
}
|
||||
|
||||
func (e *invalidRequestError) Error() string {
|
||||
return fmt.Sprintf("%d invalid request", InvalidRequestError)
|
||||
}
|
||||
|
||||
type unsupportedTypeError struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (e *unsupportedTypeError) Error() string {
|
||||
return fmt.Sprintf("%d '%s' is an unsupported type", ProtocolError, e.name)
|
||||
}
|
||||
|
||||
type callbackError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e *callbackError) Error() string {
|
||||
return fmt.Sprintf("%d %s", LogicError, e.msg)
|
||||
}
|
||||
380
rpc/ecp/protocol.go
Normal file
380
rpc/ecp/protocol.go
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ecp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
firstDelim = '\r'
|
||||
lastDelim = '\n'
|
||||
delim = "\r\n"
|
||||
simpleStrMark = '+'
|
||||
errMark = '-'
|
||||
intMark = ':'
|
||||
bulkStrMark = '$'
|
||||
arrayMark = '*'
|
||||
|
||||
methodSep = "."
|
||||
successReply = "+OK\r\n"
|
||||
)
|
||||
|
||||
type ValueType int
|
||||
|
||||
const (
|
||||
simpleStrValue ValueType = 1 << iota
|
||||
integerValue
|
||||
binaryValue
|
||||
arrayValue
|
||||
errorValue
|
||||
nullValue
|
||||
)
|
||||
|
||||
// ServerCodec implements reading and writing RPC messages for the server side of a RPC session.
|
||||
type ServerCodec interface {
|
||||
// Read the next request
|
||||
Read() (*request, error)
|
||||
// WriteResponse writes an array of values as a response to the client
|
||||
WriteResponse([]interface{}) error
|
||||
// WriteError writes an error to the client
|
||||
WriteError(error) error
|
||||
// Close the underlying data stream(s)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// ECPCodec implements the REdis Server Protocol (RESP).
|
||||
// See for more information http://redis.io/topics/protocol.
|
||||
type ECPCodec struct {
|
||||
rc io.ReadCloser
|
||||
r *bufio.Reader
|
||||
wc io.WriteCloser
|
||||
w *bufio.Writer
|
||||
}
|
||||
|
||||
// Create a new Ethereum Client Protocol Codec. It reads incoming requests from the supplied
|
||||
// reader and writes responses to the supplied writer.
|
||||
func NewECPCodec(r io.ReadCloser, w io.WriteCloser) ServerCodec {
|
||||
return &ECPCodec{r, bufio.NewReader(r), w, bufio.NewWriter(w)}
|
||||
}
|
||||
|
||||
type message struct {
|
||||
Kind ValueType
|
||||
Val interface{}
|
||||
}
|
||||
|
||||
// Close underlying reader and writer
|
||||
func (c *ECPCodec) Close() error {
|
||||
if err := c.w.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.rc.Close()
|
||||
c.wc.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read reads a new RPC request.
|
||||
func (c *ECPCodec) Read() (*request, error) {
|
||||
msg, err := c.read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if msg.Kind == arrayValue {
|
||||
if values, ok := msg.Val.([]*message); ok {
|
||||
if len(values) > 0 {
|
||||
req := new(request)
|
||||
method := strings.Split(fmt.Sprintf("%s", values[0].Val), methodSep)
|
||||
if len(method) != 2 {
|
||||
return nil, &invalidRequestError{}
|
||||
}
|
||||
|
||||
req.service, req.method = method[0], method[1]
|
||||
req.args = make([]interface{}, len(values)-1)
|
||||
for i := 1; i < len(values); i++ {
|
||||
req.args[i-1] = values[i].Val
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, &invalidRequestError{}
|
||||
}
|
||||
|
||||
func (c *ECPCodec) read() (*message, error) {
|
||||
mark, err := c.r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch mark {
|
||||
case simpleStrMark:
|
||||
return c.readSimpleStr()
|
||||
case bulkStrMark:
|
||||
return c.readBulkStr()
|
||||
case intMark:
|
||||
return c.readInteger()
|
||||
case arrayMark:
|
||||
return c.readArray()
|
||||
case errMark:
|
||||
return c.readError()
|
||||
}
|
||||
|
||||
return nil, &invalidLeadInError{mark}
|
||||
}
|
||||
|
||||
func (c *ECPCodec) readSimpleStr() (*message, error) {
|
||||
str, err := c.r.ReadBytes(firstDelim)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nl, err := c.r.ReadByte(); err != nil {
|
||||
return nil, err
|
||||
} else if nl != lastDelim {
|
||||
return nil, &unexpectedByteError{lastDelim, nl}
|
||||
}
|
||||
|
||||
return &message{simpleStrValue, string(str[:len(str)-1])}, nil
|
||||
}
|
||||
|
||||
func (c *ECPCodec) readError() (*message, error) {
|
||||
e, err := c.r.ReadBytes(firstDelim)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nl, err := c.r.ReadByte(); err != nil {
|
||||
return nil, err
|
||||
} else if nl != lastDelim {
|
||||
return nil, &unexpectedByteError{lastDelim, nl}
|
||||
}
|
||||
|
||||
return &message{errorValue, string(e[:len(e)-1])}, nil
|
||||
}
|
||||
|
||||
func (c *ECPCodec) readInteger() (*message, error) {
|
||||
i, err := c.r.ReadBytes(firstDelim)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nl, err := c.r.ReadByte(); err != nil {
|
||||
return nil, err
|
||||
} else if nl != lastDelim {
|
||||
return nil, &unexpectedByteError{lastDelim, nl}
|
||||
}
|
||||
|
||||
val, err := strconv.ParseInt(string(i[:len(i)-1]), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &message{integerValue, val}, nil
|
||||
}
|
||||
|
||||
func (c *ECPCodec) readBulkStr() (*message, error) {
|
||||
n, err := c.r.ReadBytes(firstDelim)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nl, err := c.r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if nl != lastDelim {
|
||||
return nil, &unexpectedByteError{lastDelim, nl}
|
||||
}
|
||||
|
||||
cnt, err := strconv.ParseInt(string(n[:len(n)-1]), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cnt == -1 {
|
||||
return &message{nullValue, nil}, nil
|
||||
}
|
||||
if cnt < 0 {
|
||||
return nil, &invalidRequestError{}
|
||||
}
|
||||
|
||||
val := make([]byte, cnt)
|
||||
tot := int64(0)
|
||||
for tot < cnt {
|
||||
n, err := c.r.Read(val[tot:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tot += int64(n)
|
||||
}
|
||||
|
||||
if cr, err := c.r.ReadByte(); err != nil {
|
||||
return nil, err
|
||||
} else if cr != firstDelim {
|
||||
return nil, &unexpectedByteError{lastDelim, cr}
|
||||
}
|
||||
|
||||
if nl, err := c.r.ReadByte(); err != nil {
|
||||
return nil, err
|
||||
} else if nl != lastDelim {
|
||||
return nil, &unexpectedByteError{lastDelim, nl}
|
||||
}
|
||||
|
||||
return &message{binaryValue, val}, nil
|
||||
}
|
||||
|
||||
func (c *ECPCodec) readArray() (*message, error) {
|
||||
n, err := c.r.ReadBytes(firstDelim)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nl, err := c.r.ReadByte(); err != nil {
|
||||
return nil, err
|
||||
} else if nl != lastDelim {
|
||||
return nil, &unexpectedByteError{lastDelim, nl}
|
||||
}
|
||||
|
||||
nElem, err := strconv.ParseInt(string(n[:len(n)-1]), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arr := make([]*message, nElem)
|
||||
|
||||
for i := int64(0); i < nElem; i++ {
|
||||
if arr[i], err = c.read(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &message{arrayValue, arr}, nil
|
||||
}
|
||||
|
||||
// WriteResponse writes a values to the client. When values is nil the default
|
||||
// success response is written.
|
||||
func (c *ECPCodec) WriteResponse(values []interface{}) error {
|
||||
defer c.w.Flush()
|
||||
|
||||
if values == nil {
|
||||
_, e := c.w.WriteString(successReply)
|
||||
return e
|
||||
}
|
||||
|
||||
return c.writeArray(values)
|
||||
}
|
||||
|
||||
// WriteError writes the supplied error to the client
|
||||
func (c *ECPCodec) WriteError(err error) error {
|
||||
defer c.w.Flush()
|
||||
_, e := c.w.WriteString(fmt.Sprintf("%c%s%s", errMark, err, delim))
|
||||
return e
|
||||
}
|
||||
|
||||
func (c *ECPCodec) writeSimpleString(str string) error {
|
||||
_, err := c.w.WriteString(fmt.Sprintf("%c%s%s", simpleStrMark, str, delim))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ECPCodec) writeInteger(i int64) error {
|
||||
_, err := c.w.WriteString(fmt.Sprintf("%c%d%s", intMark, i, delim))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ECPCodec) writeBinary(bin []byte) error {
|
||||
if bin == nil {
|
||||
_, err := c.w.WriteString(fmt.Sprintf("%c-1%s", bulkStrMark, delim))
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := c.w.WriteString(fmt.Sprintf("%c%d%s", bulkStrMark, len(bin), delim)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := c.w.Write(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := c.w.WriteString(delim)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ECPCodec) writeStruct(obj interface{}) error {
|
||||
val := reflect.ValueOf(obj)
|
||||
fields := make([]interface{}, val.NumField())
|
||||
cnt := 0
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
if val.Field(i).CanInterface() { // skip unexported fields
|
||||
fields[cnt] = val.Field(i).Interface()
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
return c.writeArray(fields[:cnt])
|
||||
}
|
||||
|
||||
func (c *ECPCodec) writeArray(arr []interface{}) (err error) {
|
||||
if arr == nil {
|
||||
_, err = c.w.WriteString(fmt.Sprintf("%c-1%s", arrayMark, delim))
|
||||
return
|
||||
}
|
||||
|
||||
// array length prefix
|
||||
if _, err = c.w.WriteString(fmt.Sprintf("%c%d%s", arrayMark, len(arr), delim)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(arr) && err == nil; i++ {
|
||||
switch val := arr[i].(type) {
|
||||
case string:
|
||||
err = c.writeSimpleString(val)
|
||||
case *string:
|
||||
err = c.writeSimpleString(*val)
|
||||
case []byte:
|
||||
err = c.writeBinary(val)
|
||||
case big.Int:
|
||||
err = c.writeBinary(val.Bytes())
|
||||
case *big.Int:
|
||||
err = c.writeBinary(val.Bytes())
|
||||
case int64:
|
||||
err = c.writeInteger(val)
|
||||
case nil: // use nil bulk string to represent nil values
|
||||
err = c.writeBinary(nil)
|
||||
case error:
|
||||
err = c.WriteError(val)
|
||||
default:
|
||||
typ := reflect.TypeOf(val)
|
||||
if typ.Kind() == reflect.Struct {
|
||||
err = c.writeStruct(val)
|
||||
} else if a, ok := val.([]interface{}); ok {
|
||||
err = c.writeArray(a)
|
||||
} else {
|
||||
err = &unsupportedTypeError{typ.Name()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
107
rpc/ecp/protocol_test.go
Normal file
107
rpc/ecp/protocol_test.go
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
package ecp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testReader struct {
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func (r *testReader) Read(p []byte) (n int, err error) {
|
||||
return r.r.Read(p)
|
||||
}
|
||||
|
||||
func (r *testReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newTestReader(source string) io.ReadCloser {
|
||||
return &testReader{strings.NewReader(source)}
|
||||
}
|
||||
|
||||
func TestNoArgsAndRets(t *testing.T) {
|
||||
r := newTestReader("*1\r\n+Service.Method\r\n")
|
||||
codec := NewECPCodec(r, nil)
|
||||
|
||||
req, err := codec.Read()
|
||||
if err != nil {
|
||||
t.Fatalf("%s\n", err)
|
||||
}
|
||||
|
||||
if req.service != "Service" {
|
||||
t.Fatalf("Expected service 'Service' but got '%s'\n", req.service)
|
||||
}
|
||||
|
||||
if req.method != "Method" {
|
||||
t.Fatalf("Expected method 'Method' but got '%s'\n", req.method)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithArgs(t *testing.T) {
|
||||
i := big.NewInt(123456789)
|
||||
ib := i.Bytes()
|
||||
str := "some string"
|
||||
bstr := []byte{'a', '\r', '\n', 'b'}
|
||||
in := int64(42)
|
||||
|
||||
r := newTestReader(fmt.Sprintf("*5\r\n+Service.Method\r\n$%d\r\n%s\r\n+%s\r\n$%d\r\n%s\r\n:%d\r\n",
|
||||
len(ib), ib, str, len(bstr), bstr, in))
|
||||
|
||||
codec := NewECPCodec(r, nil)
|
||||
|
||||
req, err := codec.Read()
|
||||
if err != nil {
|
||||
t.Fatalf("%s\n", err)
|
||||
}
|
||||
|
||||
if req.service != "Service" {
|
||||
t.Fatalf("Expected service 'Service' but got '%s'\n", req.service)
|
||||
}
|
||||
|
||||
if req.method != "Method" {
|
||||
t.Fatalf("Expected method 'Method' but got '%s'\n", req.method)
|
||||
}
|
||||
|
||||
if len(req.args) != 4 {
|
||||
t.Fatalf("Expected 3 args, got %d args(s)\n", len(req.args))
|
||||
}
|
||||
|
||||
if pin, ok := req.args[0].([]byte); ok {
|
||||
parsedInt := new(big.Int).SetBytes(pin)
|
||||
if parsedInt.Cmp(i) != 0 {
|
||||
t.Fatalf("Expected big.Int(%d).Cmp(%d) == 0\n", parsedInt, i)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("Expected arg[0] to be []byte, got %T\n", req.args[0])
|
||||
}
|
||||
|
||||
if s, ok := req.args[1].(string); ok {
|
||||
if str != s {
|
||||
t.Fatalf("Expected 'some string' == '%s'\n", s)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("Expected arg[1] to be string, got %T\n", req.args[1])
|
||||
}
|
||||
|
||||
if arr, ok := req.args[2].([]byte); ok {
|
||||
if bytes.Compare(arr, bstr) != 0 {
|
||||
t.Fatalf("Expected %s, got %s\n", bstr, arr)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("Expected arg[2] to be []byte, got %T\n", req.args[2])
|
||||
}
|
||||
|
||||
if val, ok := req.args[3].(int64); ok {
|
||||
if in != val {
|
||||
t.Fatalf("Expected %d, got %d\n", in, val)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("Expected arg[3] to be int64, got %T\n", req.args[3])
|
||||
}
|
||||
}
|
||||
258
rpc/ecp/server.go
Normal file
258
rpc/ecp/server.go
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ecp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
)
|
||||
|
||||
type request struct {
|
||||
service string // registered service name
|
||||
method string // callback on service
|
||||
args []interface{} // arguments for callback
|
||||
}
|
||||
|
||||
type callback struct {
|
||||
method reflect.Method // callback
|
||||
args []reflect.Type // input argument types
|
||||
errPos int // err return idx, of -1 when method cannot return error
|
||||
}
|
||||
|
||||
type service struct {
|
||||
name string // name for service
|
||||
rcvr reflect.Value // receiver of methods for the service
|
||||
typ reflect.Type // receiver type
|
||||
callbacks callbacks // registered handlers
|
||||
}
|
||||
|
||||
type serviceRegistry map[string]*service
|
||||
type codecCollection map[ServerCodec]interface{}
|
||||
type callbacks map[string]*callback
|
||||
|
||||
// Server represents a RPC server
|
||||
type Server struct {
|
||||
listener net.Listener
|
||||
serviceMap serviceRegistry
|
||||
mu sync.Mutex // protects codec collection
|
||||
codecs codecCollection // active connections/codecs
|
||||
}
|
||||
|
||||
// NewServer will create a new server instance with no registered handlers.
|
||||
func NewServer() *Server {
|
||||
return &Server{
|
||||
serviceMap: make(serviceRegistry),
|
||||
codecs: make(codecCollection),
|
||||
}
|
||||
}
|
||||
|
||||
// Register publishes suitable methods of rcvr. Methods will be published
|
||||
// with a dot between the rcrv and method, e.g. ChainManager.GetBlock.
|
||||
func (s *Server) Register(rcvr interface{}) error {
|
||||
return s.register(rcvr, "", false)
|
||||
}
|
||||
|
||||
// Register publishes suitable methods of rcvr. Methods will be published
|
||||
// with a dot between the given name and method, e.g. ChainManager.GetBlock.
|
||||
func (s *Server) RegisterName(name string, rcvr interface{}) error {
|
||||
return s.register(rcvr, name, true)
|
||||
}
|
||||
|
||||
func (s *Server) register(rcvr interface{}, name string, useName bool) error {
|
||||
if s.serviceMap == nil {
|
||||
s.serviceMap = make(map[string]*service)
|
||||
}
|
||||
|
||||
svc := new(service)
|
||||
svc.typ = reflect.TypeOf(rcvr)
|
||||
svc.rcvr = reflect.ValueOf(rcvr)
|
||||
|
||||
sname := reflect.Indirect(svc.rcvr).Type().Name()
|
||||
if useName {
|
||||
sname = name
|
||||
}
|
||||
if sname == "" {
|
||||
return fmt.Errorf("no service name for type %s", svc.typ.String())
|
||||
}
|
||||
if !isExported(sname) && !useName {
|
||||
return fmt.Errorf("%s is not exported", sname)
|
||||
}
|
||||
|
||||
if _, present := s.serviceMap[sname]; present {
|
||||
return fmt.Errorf("%s already registered", sname)
|
||||
}
|
||||
|
||||
svc.name = sname
|
||||
svc.callbacks = suitableCallbacks(svc.typ)
|
||||
|
||||
if len(svc.callbacks) == 0 {
|
||||
return fmt.Errorf("Service doesn't have any suitable methods to expose")
|
||||
}
|
||||
|
||||
s.serviceMap[svc.name] = svc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serve accepts connections and starts processing incoming requests.
|
||||
func (s *Server) Serve(l net.Listener) {
|
||||
s.listener = l
|
||||
for {
|
||||
c, err := s.listener.Accept()
|
||||
if err != nil {
|
||||
glog.V(logger.Debug).Infof("%v\n", err)
|
||||
break
|
||||
}
|
||||
|
||||
glog.V(logger.Debug).Infof("Accepted connection from %s\n", c.RemoteAddr())
|
||||
codec := NewECPCodec(c, c)
|
||||
s.mu.Lock()
|
||||
s.codecs[codec] = nil
|
||||
s.mu.Unlock()
|
||||
go s.serveCodec(codec)
|
||||
}
|
||||
|
||||
// stopped accepting new connections, close existing
|
||||
for c, _ := range s.codecs {
|
||||
s.close(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop will stop listening for new connection and closes existing connections
|
||||
func (s *Server) Stop() error {
|
||||
if s.listener != nil {
|
||||
return s.listener.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// serveCodec start the server and uses the supplied codec to read and write
|
||||
// requests and response. It will call Close on the codec when it returns.
|
||||
func (s *Server) serveCodec(codec ServerCodec) error {
|
||||
defer s.close(codec)
|
||||
|
||||
for {
|
||||
svc, c, argv, err := s.readRequest(codec)
|
||||
if err != nil {
|
||||
err2 := codec.WriteError(err)
|
||||
if !isRecoverable(err) {
|
||||
glog.V(logger.Error).Infoln(err)
|
||||
return err
|
||||
}
|
||||
if !isRecoverable(err2) {
|
||||
glog.V(logger.Error).Infoln(err2)
|
||||
return err2
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if res, err := s.call(c, svc.rcvr, argv); err != nil {
|
||||
if err = codec.WriteError(err); err != nil {
|
||||
if !isRecoverable(err) {
|
||||
glog.V(logger.Error).Infoln(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if res == nil { // callback has no return values
|
||||
if err = codec.WriteResponse(nil); err != nil {
|
||||
if !isRecoverable(err) {
|
||||
glog.V(logger.Error).Infoln(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
values := make([]interface{}, len(res))
|
||||
for i := 0; i < len(res); i++ {
|
||||
values[i] = res[i].Interface()
|
||||
}
|
||||
|
||||
if err = codec.WriteResponse(values); err != nil {
|
||||
if !isRecoverable(err) {
|
||||
glog.V(logger.Error).Infoln(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) close(codec ServerCodec) {
|
||||
codec.Close()
|
||||
s.mu.Lock()
|
||||
delete(s.codecs, codec)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *Server) call(c *callback, rcvr reflect.Value, argv []reflect.Value) ([]reflect.Value, error) {
|
||||
args := make([]reflect.Value, 1+len(argv))
|
||||
args[0] = rcvr
|
||||
for i := 0; i < len(argv); i++ {
|
||||
args[i+1] = argv[i]
|
||||
}
|
||||
rets := c.method.Func.Call(args)
|
||||
|
||||
// check for error
|
||||
if c.errPos >= 0 && !rets[c.errPos].IsNil() {
|
||||
return nil, &callbackError{fmt.Sprintf("%s", rets[c.errPos])}
|
||||
}
|
||||
|
||||
// don't send error when there is none
|
||||
if c.errPos > 0 {
|
||||
return rets[:c.errPos], nil
|
||||
}
|
||||
|
||||
return rets, nil
|
||||
}
|
||||
|
||||
func (s *Server) readRequest(codec ServerCodec) (svc *service, c *callback, argv []reflect.Value, err error) {
|
||||
req, err := codec.Read()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// determine callback
|
||||
if svc = s.serviceMap[req.service]; svc == nil {
|
||||
return nil, nil, nil, &unknownServiceError{req.service}
|
||||
}
|
||||
|
||||
if c = svc.callbacks[req.method]; c == nil {
|
||||
return nil, nil, nil, &unknownMethodError{req.service, req.method}
|
||||
}
|
||||
|
||||
// decode params
|
||||
nArgs := len(c.args)
|
||||
if nArgs != len(req.args) {
|
||||
return nil, nil, nil, &invalidNumberOfArgumentsError{req.service, req.method, nArgs, len(req.args)}
|
||||
}
|
||||
|
||||
argv = make([]reflect.Value, nArgs)
|
||||
for i := 0; i < nArgs; i++ {
|
||||
argv[i], err = convert(i, req.args[i], c.args[i])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
167
rpc/ecp/utils.go
Normal file
167
rpc/ecp/utils.go
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ecp
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Is this an exported - upper case - name?
|
||||
func isExported(name string) bool {
|
||||
rune, _ := utf8.DecodeRuneInString(name)
|
||||
return unicode.IsUpper(rune)
|
||||
}
|
||||
|
||||
// Is this type exported or a builtin?
|
||||
func isExportedOrBuiltinType(t reflect.Type) bool {
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
// PkgPath will be non-empty even for an exported type,
|
||||
// so we need to check the type name as well.
|
||||
return isExported(t.Name()) || t.PkgPath() == ""
|
||||
}
|
||||
|
||||
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
// Implements this type the error interface
|
||||
func isErrorType(t reflect.Type) bool {
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
return t.Implements(errorType)
|
||||
}
|
||||
|
||||
func suitableCallbacks(typ reflect.Type) callbacks {
|
||||
callbacks := make(callbacks)
|
||||
METHODS:
|
||||
for m := 0; m < typ.NumMethod(); m++ {
|
||||
method := typ.Method(m)
|
||||
mtype := method.Type
|
||||
mname := method.Name
|
||||
if method.PkgPath != "" { // method must be exported
|
||||
continue
|
||||
}
|
||||
|
||||
var h callback
|
||||
h.method = method
|
||||
numIn := mtype.NumIn()
|
||||
|
||||
// determine arguments, ignore first arg since it's the receiver type
|
||||
h.args = make([]reflect.Type, numIn-1)
|
||||
for i := 1; i < numIn; i++ {
|
||||
argType := mtype.In(i)
|
||||
if !isExportedOrBuiltinType(argType) {
|
||||
continue METHODS
|
||||
}
|
||||
h.args[i-1] = argType
|
||||
}
|
||||
|
||||
// determine if callback can return an error
|
||||
h.errPos = -1
|
||||
for i := 0; i < mtype.NumOut(); i++ {
|
||||
if isErrorType(mtype.Out(i)) {
|
||||
h.errPos = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// only support methods which return an error as the last returned value (or no error).
|
||||
if h.errPos == -1 || (h.errPos >= 0 && h.errPos == mtype.NumOut()-1) {
|
||||
callbacks[mname] = &h
|
||||
}
|
||||
}
|
||||
|
||||
return callbacks
|
||||
}
|
||||
|
||||
var bigIntType = reflect.TypeOf(new(big.Int))
|
||||
|
||||
func isBigInt(typ reflect.Type) bool {
|
||||
return typ == bigIntType
|
||||
}
|
||||
|
||||
// in the future we might consider adding support for more conversion as the API gets defined
|
||||
func convert(pos int, val interface{}, to reflect.Type) (reflect.Value, error) {
|
||||
from := reflect.TypeOf(val)
|
||||
if from.Kind() == to.Kind() {
|
||||
return reflect.ValueOf(val), nil
|
||||
}
|
||||
|
||||
plainType := to
|
||||
for plainType.Kind() == reflect.Ptr {
|
||||
plainType = plainType.Elem()
|
||||
}
|
||||
|
||||
if plainType.Kind() == reflect.Struct {
|
||||
return convertStruct(pos, val, to, plainType)
|
||||
}
|
||||
|
||||
retPtr := reflect.New(plainType)
|
||||
if from.ConvertibleTo(plainType) {
|
||||
ret := reflect.Indirect(retPtr)
|
||||
ret.Set(reflect.ValueOf(val).Convert(plainType))
|
||||
if to.Kind() == reflect.Ptr {
|
||||
return retPtr, nil
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
if bytes, ok := val.([]byte); ok && to.Kind() == reflect.Int64 {
|
||||
if i, err := strconv.ParseInt(string(bytes), 10, 64); err == nil {
|
||||
return reflect.ValueOf(i), nil
|
||||
}
|
||||
}
|
||||
|
||||
if bytes, ok := val.([]byte); ok && isBigInt(to) {
|
||||
v := new(big.Int)
|
||||
v.SetBytes(bytes)
|
||||
return reflect.ValueOf(v), nil
|
||||
}
|
||||
|
||||
return reflect.Zero(to), &invalidArgumentError{1 + pos, to, reflect.TypeOf(val)}
|
||||
}
|
||||
|
||||
func convertStruct(pos int, val interface{}, to, plainType reflect.Type) (reflect.Value, error) {
|
||||
if fields, ok := val.([]*message); ok {
|
||||
if len(fields) != plainType.NumField() {
|
||||
return reflect.Zero(to), &invalidStructArgumentError{to, plainType.NumField(), len(fields)}
|
||||
}
|
||||
|
||||
ptrVal := reflect.New(plainType)
|
||||
v := reflect.Indirect(ptrVal)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
val, err := convert(pos, fields[i].Val, v.Field(i).Type())
|
||||
if err != nil {
|
||||
return reflect.Zero(to), err
|
||||
}
|
||||
v.Field(i).Set(val)
|
||||
}
|
||||
|
||||
if to.Kind() == reflect.Ptr {
|
||||
return ptrVal, nil
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return reflect.Zero(to), &invalidArgumentError{1 + pos, to, reflect.TypeOf(val)}
|
||||
}
|
||||
Loading…
Reference in a new issue