forked from cerc-io/ipld-eth-server
Update dependencies
- uses newer version of go-ethereum required for go1.11
This commit is contained in:
+49
-8
@@ -25,6 +25,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -60,7 +61,7 @@ const (
|
||||
// The approach taken here is to maintain a per-subscription linked list buffer
|
||||
// shrinks on demand. If the buffer reaches the size below, the subscription is
|
||||
// dropped.
|
||||
maxClientSubscriptionBuffer = 8000
|
||||
maxClientSubscriptionBuffer = 20000
|
||||
)
|
||||
|
||||
// BatchElem is an element in a batch request.
|
||||
@@ -171,6 +172,8 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) {
|
||||
return DialHTTP(rawurl)
|
||||
case "ws", "wss":
|
||||
return DialWebsocket(ctx, rawurl, "")
|
||||
case "stdio":
|
||||
return DialStdIO(ctx)
|
||||
case "":
|
||||
return DialIPC(ctx, rawurl)
|
||||
default:
|
||||
@@ -178,13 +181,51 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) {
|
||||
}
|
||||
}
|
||||
|
||||
type StdIOConn struct{}
|
||||
|
||||
func (io StdIOConn) Read(b []byte) (n int, err error) {
|
||||
return os.Stdin.Read(b)
|
||||
}
|
||||
|
||||
func (io StdIOConn) Write(b []byte) (n int, err error) {
|
||||
return os.Stdout.Write(b)
|
||||
}
|
||||
|
||||
func (io StdIOConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (io StdIOConn) LocalAddr() net.Addr {
|
||||
return &net.UnixAddr{Name: "stdio", Net: "stdio"}
|
||||
}
|
||||
|
||||
func (io StdIOConn) RemoteAddr() net.Addr {
|
||||
return &net.UnixAddr{Name: "stdio", Net: "stdio"}
|
||||
}
|
||||
|
||||
func (io StdIOConn) SetDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (io StdIOConn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (io StdIOConn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
func DialStdIO(ctx context.Context) (*Client, error) {
|
||||
return newClient(ctx, func(_ context.Context) (net.Conn, error) {
|
||||
return StdIOConn{}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func newClient(initctx context.Context, connectFunc func(context.Context) (net.Conn, error)) (*Client, error) {
|
||||
conn, err := connectFunc(initctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, isHTTP := conn.(*httpConn)
|
||||
|
||||
c := &Client{
|
||||
writeConn: conn,
|
||||
isHTTP: isHTTP,
|
||||
@@ -263,7 +304,7 @@ func (c *Client) CallContext(ctx context.Context, result interface{}, method str
|
||||
return err
|
||||
}
|
||||
|
||||
// dispatch has accepted the request and will close the channel it when it quits.
|
||||
// dispatch has accepted the request and will close the channel when it quits.
|
||||
switch resp, err := op.wait(ctx); {
|
||||
case err != nil:
|
||||
return err
|
||||
@@ -524,13 +565,13 @@ func (c *Client) dispatch(conn net.Conn) {
|
||||
}
|
||||
|
||||
case err := <-c.readErr:
|
||||
log.Debug(fmt.Sprintf("<-readErr: %v", err))
|
||||
log.Debug("<-readErr", "err", err)
|
||||
c.closeRequestOps(err)
|
||||
conn.Close()
|
||||
reading = false
|
||||
|
||||
case newconn := <-c.reconnected:
|
||||
log.Debug(fmt.Sprintf("<-reconnected: (reading=%t) %v", reading, conn.RemoteAddr()))
|
||||
log.Debug("<-reconnected", "reading", reading, "remote", conn.RemoteAddr())
|
||||
if reading {
|
||||
// Wait for the previous read loop to exit. This is a rare case.
|
||||
conn.Close()
|
||||
@@ -587,7 +628,7 @@ func (c *Client) closeRequestOps(err error) {
|
||||
|
||||
func (c *Client) handleNotification(msg *jsonrpcMessage) {
|
||||
if !strings.HasSuffix(msg.Method, notificationMethodSuffix) {
|
||||
log.Debug(fmt.Sprint("dropping non-subscription message: ", msg))
|
||||
log.Debug("dropping non-subscription message", "msg", msg)
|
||||
return
|
||||
}
|
||||
var subResult struct {
|
||||
@@ -595,7 +636,7 @@ func (c *Client) handleNotification(msg *jsonrpcMessage) {
|
||||
Result json.RawMessage `json:"result"`
|
||||
}
|
||||
if err := json.Unmarshal(msg.Params, &subResult); err != nil {
|
||||
log.Debug(fmt.Sprint("dropping invalid subscription message: ", msg))
|
||||
log.Debug("dropping invalid subscription message", "msg", msg)
|
||||
return
|
||||
}
|
||||
if c.subs[subResult.ID] != nil {
|
||||
@@ -606,7 +647,7 @@ func (c *Client) handleNotification(msg *jsonrpcMessage) {
|
||||
func (c *Client) handleResponse(msg *jsonrpcMessage) {
|
||||
op := c.respWait[string(msg.ID)]
|
||||
if op == nil {
|
||||
log.Debug(fmt.Sprintf("unsolicited response %v", msg))
|
||||
log.Debug("unsolicited response", "msg", msg)
|
||||
return
|
||||
}
|
||||
delete(c.respWait, string(msg.ID))
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ func subscribeBlocks(client *rpc.Client, subch chan Block) {
|
||||
defer cancel()
|
||||
|
||||
// Subscribe to new blocks.
|
||||
sub, err := client.EthSubscribe(ctx, subch, "newBlocks")
|
||||
sub, err := client.EthSubscribe(ctx, subch, "newHeads")
|
||||
if err != nil {
|
||||
fmt.Println("subscribe error:", err)
|
||||
return
|
||||
|
||||
+2
-2
@@ -58,7 +58,7 @@ An example server which uses the JSON codec:
|
||||
return a + b
|
||||
}
|
||||
|
||||
func (s *CalculatorService Div(a, b int) (int, error) {
|
||||
func (s *CalculatorService) Div(a, b int) (int, error) {
|
||||
if b == 0 {
|
||||
return 0, errors.New("divide by zero")
|
||||
}
|
||||
@@ -92,7 +92,7 @@ An example method:
|
||||
Subscriptions are deleted when:
|
||||
- the user sends an unsubscribe request
|
||||
- the connection which was used to create the subscription is closed. This can be initiated
|
||||
by the client and server. The server will close the connection on an write error or when
|
||||
by the client and server. The server will close the connection on a write error or when
|
||||
the queue of buffered notifications gets too big.
|
||||
*/
|
||||
package rpc
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
// Copyright 2018 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 rpc
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// StartHTTPEndpoint starts the HTTP RPC endpoint, configured with cors/vhosts/modules
|
||||
func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []string, vhosts []string, timeouts HTTPTimeouts) (net.Listener, *Server, error) {
|
||||
// Generate the whitelist based on the allowed modules
|
||||
whitelist := make(map[string]bool)
|
||||
for _, module := range modules {
|
||||
whitelist[module] = true
|
||||
}
|
||||
// Register all the APIs exposed by the services
|
||||
handler := NewServer()
|
||||
for _, api := range apis {
|
||||
if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
|
||||
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
log.Debug("HTTP registered", "namespace", api.Namespace)
|
||||
}
|
||||
}
|
||||
// All APIs registered, start the HTTP listener
|
||||
var (
|
||||
listener net.Listener
|
||||
err error
|
||||
)
|
||||
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
go NewHTTPServer(cors, vhosts, timeouts, handler).Serve(listener)
|
||||
return listener, handler, err
|
||||
}
|
||||
|
||||
// StartWSEndpoint starts a websocket endpoint
|
||||
func StartWSEndpoint(endpoint string, apis []API, modules []string, wsOrigins []string, exposeAll bool) (net.Listener, *Server, error) {
|
||||
|
||||
// Generate the whitelist based on the allowed modules
|
||||
whitelist := make(map[string]bool)
|
||||
for _, module := range modules {
|
||||
whitelist[module] = true
|
||||
}
|
||||
// Register all the APIs exposed by the services
|
||||
handler := NewServer()
|
||||
for _, api := range apis {
|
||||
if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
|
||||
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
log.Debug("WebSocket registered", "service", api.Service, "namespace", api.Namespace)
|
||||
}
|
||||
}
|
||||
// All APIs registered, start the HTTP listener
|
||||
var (
|
||||
listener net.Listener
|
||||
err error
|
||||
)
|
||||
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
go NewWSServer(wsOrigins, handler).Serve(listener)
|
||||
return listener, handler, err
|
||||
|
||||
}
|
||||
|
||||
// StartIPCEndpoint starts an IPC endpoint.
|
||||
func StartIPCEndpoint(ipcEndpoint string, apis []API) (net.Listener, *Server, error) {
|
||||
// Register all the APIs exposed by the services.
|
||||
handler := NewServer()
|
||||
for _, api := range apis {
|
||||
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
log.Debug("IPC registered", "namespace", api.Namespace)
|
||||
}
|
||||
// All APIs registered, start the IPC listener.
|
||||
listener, err := ipcListen(ipcEndpoint)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
go handler.ServeListener(listener)
|
||||
return listener, handler, nil
|
||||
}
|
||||
+81
-10
@@ -27,16 +27,17 @@ import (
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/rs/cors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
contentType = "application/json"
|
||||
maxHTTPRequestContentLength = 1024 * 128
|
||||
contentType = "application/json"
|
||||
maxRequestContentLength = 1024 * 128
|
||||
)
|
||||
|
||||
var nullAddr, _ = net.ResolveTCPAddr("tcp", "127.0.0.1:0")
|
||||
@@ -66,6 +67,38 @@ func (hc *httpConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// HTTPTimeouts represents the configuration params for the HTTP RPC server.
|
||||
type HTTPTimeouts struct {
|
||||
// ReadTimeout is the maximum duration for reading the entire
|
||||
// request, including the body.
|
||||
//
|
||||
// Because ReadTimeout does not let Handlers make per-request
|
||||
// decisions on each request body's acceptable deadline or
|
||||
// upload rate, most users will prefer to use
|
||||
// ReadHeaderTimeout. It is valid to use them both.
|
||||
ReadTimeout time.Duration
|
||||
|
||||
// WriteTimeout is the maximum duration before timing out
|
||||
// writes of the response. It is reset whenever a new
|
||||
// request's header is read. Like ReadTimeout, it does not
|
||||
// let Handlers make decisions on a per-request basis.
|
||||
WriteTimeout time.Duration
|
||||
|
||||
// IdleTimeout is the maximum amount of time to wait for the
|
||||
// next request when keep-alives are enabled. If IdleTimeout
|
||||
// is zero, the value of ReadTimeout is used. If both are
|
||||
// zero, ReadHeaderTimeout is used.
|
||||
IdleTimeout time.Duration
|
||||
}
|
||||
|
||||
// DefaultHTTPTimeouts represents the default timeout values used if further
|
||||
// configuration is not provided.
|
||||
var DefaultHTTPTimeouts = HTTPTimeouts{
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
}
|
||||
|
||||
// DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP
|
||||
// using the provided HTTP Client.
|
||||
func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) {
|
||||
@@ -90,10 +123,19 @@ func DialHTTP(endpoint string) (*Client, error) {
|
||||
func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error {
|
||||
hc := c.writeConn.(*httpConn)
|
||||
respBody, err := hc.doRequest(ctx, msg)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if respBody != nil {
|
||||
buf := new(bytes.Buffer)
|
||||
if _, err2 := buf.ReadFrom(respBody); err2 == nil {
|
||||
return fmt.Errorf("%v %v", err, buf.String())
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer respBody.Close()
|
||||
var respmsg jsonrpcMessage
|
||||
if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil {
|
||||
return err
|
||||
@@ -132,6 +174,9 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return resp.Body, errors.New(resp.Status)
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
@@ -149,11 +194,31 @@ func (t *httpReadWriteNopCloser) Close() error {
|
||||
// NewHTTPServer creates a new HTTP RPC server around an API provider.
|
||||
//
|
||||
// Deprecated: Server implements http.Handler
|
||||
func NewHTTPServer(cors []string, vhosts []string, srv *Server) *http.Server {
|
||||
func NewHTTPServer(cors []string, vhosts []string, timeouts HTTPTimeouts, srv *Server) *http.Server {
|
||||
// Wrap the CORS-handler within a host-handler
|
||||
handler := newCorsHandler(srv, cors)
|
||||
handler = newVHostHandler(vhosts, handler)
|
||||
return &http.Server{Handler: handler}
|
||||
|
||||
// Make sure timeout values are meaningful
|
||||
if timeouts.ReadTimeout < time.Second {
|
||||
log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", DefaultHTTPTimeouts.ReadTimeout)
|
||||
timeouts.ReadTimeout = DefaultHTTPTimeouts.ReadTimeout
|
||||
}
|
||||
if timeouts.WriteTimeout < time.Second {
|
||||
log.Warn("Sanitizing invalid HTTP write timeout", "provided", timeouts.WriteTimeout, "updated", DefaultHTTPTimeouts.WriteTimeout)
|
||||
timeouts.WriteTimeout = DefaultHTTPTimeouts.WriteTimeout
|
||||
}
|
||||
if timeouts.IdleTimeout < time.Second {
|
||||
log.Warn("Sanitizing invalid HTTP idle timeout", "provided", timeouts.IdleTimeout, "updated", DefaultHTTPTimeouts.IdleTimeout)
|
||||
timeouts.IdleTimeout = DefaultHTTPTimeouts.IdleTimeout
|
||||
}
|
||||
// Bundle and start the HTTP server
|
||||
return &http.Server{
|
||||
Handler: handler,
|
||||
ReadTimeout: timeouts.ReadTimeout,
|
||||
WriteTimeout: timeouts.WriteTimeout,
|
||||
IdleTimeout: timeouts.IdleTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP serves JSON-RPC requests over HTTP.
|
||||
@@ -169,11 +234,17 @@ func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// All checks passed, create a codec that reads direct from the request body
|
||||
// untilEOF and writes the response to w and order the server to process a
|
||||
// single request.
|
||||
codec := NewJSONCodec(&httpReadWriteNopCloser{r.Body, w})
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, "remote", r.RemoteAddr)
|
||||
ctx = context.WithValue(ctx, "scheme", r.Proto)
|
||||
ctx = context.WithValue(ctx, "local", r.Host)
|
||||
|
||||
body := io.LimitReader(r.Body, maxRequestContentLength)
|
||||
codec := NewJSONCodec(&httpReadWriteNopCloser{body, w})
|
||||
defer codec.Close()
|
||||
|
||||
w.Header().Set("content-type", contentType)
|
||||
srv.ServeSingleRequest(codec, OptionMethodInvocation)
|
||||
srv.ServeSingleRequest(ctx, codec, OptionMethodInvocation)
|
||||
}
|
||||
|
||||
// validateRequest returns a non-zero response code and error message if the
|
||||
@@ -182,8 +253,8 @@ func validateRequest(r *http.Request) (int, error) {
|
||||
if r.Method == http.MethodPut || r.Method == http.MethodDelete {
|
||||
return http.StatusMethodNotAllowed, errors.New("method not allowed")
|
||||
}
|
||||
if r.ContentLength > maxHTTPRequestContentLength {
|
||||
err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxHTTPRequestContentLength)
|
||||
if r.ContentLength > maxRequestContentLength {
|
||||
err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength)
|
||||
return http.StatusRequestEntityTooLarge, err
|
||||
}
|
||||
mt, _, err := mime.ParseMediaType(r.Header.Get("content-type"))
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ func TestHTTPErrorResponseWithPut(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHTTPErrorResponseWithMaxContentLength(t *testing.T) {
|
||||
body := make([]rune, maxHTTPRequestContentLength+1)
|
||||
body := make([]rune, maxRequestContentLength+1)
|
||||
testHTTPErrorResponse(t,
|
||||
http.MethodPost, contentType, string(body), http.StatusRequestEntityTooLarge)
|
||||
}
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// NewInProcClient attaches an in-process connection to the given RPC server.
|
||||
// DialInProc attaches an in-process connection to the given RPC server.
|
||||
func DialInProc(handler *Server) *Client {
|
||||
initctx := context.Background()
|
||||
c, _ := newClient(initctx, func(context.Context) (net.Conn, error) {
|
||||
|
||||
+6
-9
@@ -18,26 +18,23 @@ package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
)
|
||||
|
||||
// CreateIPCListener creates an listener, on Unix platforms this is a unix socket, on
|
||||
// Windows this is a named pipe
|
||||
func CreateIPCListener(endpoint string) (net.Listener, error) {
|
||||
return ipcListen(endpoint)
|
||||
}
|
||||
|
||||
// ServeListener accepts connections on l, serving JSON-RPC on them.
|
||||
func (srv *Server) ServeListener(l net.Listener) error {
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
if netutil.IsTemporaryError(err) {
|
||||
log.Warn("RPC accept error", "err", err)
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Trace(fmt.Sprint("accepted conn", conn.RemoteAddr()))
|
||||
log.Trace("Accepted connection", "addr", conn.RemoteAddr())
|
||||
go srv.ServeCodec(NewJSONCodec(conn), OptionMethodInvocation|OptionSubscriptions)
|
||||
}
|
||||
}
|
||||
|
||||
+31
-23
@@ -76,13 +76,13 @@ type jsonNotification struct {
|
||||
// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It
|
||||
// also has support for parsing arguments and serializing (result) objects.
|
||||
type jsonCodec struct {
|
||||
closer sync.Once // close closed channel once
|
||||
closed chan interface{} // closed on Close
|
||||
decMu sync.Mutex // guards d
|
||||
d *json.Decoder // decodes incoming requests
|
||||
encMu sync.Mutex // guards e
|
||||
e *json.Encoder // encodes responses
|
||||
rw io.ReadWriteCloser // connection
|
||||
closer sync.Once // close closed channel once
|
||||
closed chan interface{} // closed on Close
|
||||
decMu sync.Mutex // guards the decoder
|
||||
decode func(v interface{}) error // decoder to allow multiple transports
|
||||
encMu sync.Mutex // guards the encoder
|
||||
encode func(v interface{}) error // encoder to allow multiple transports
|
||||
rw io.ReadWriteCloser // connection
|
||||
}
|
||||
|
||||
func (err *jsonError) Error() string {
|
||||
@@ -96,11 +96,29 @@ func (err *jsonError) ErrorCode() int {
|
||||
return err.Code
|
||||
}
|
||||
|
||||
// NewJSONCodec creates a new RPC server codec with support for JSON-RPC 2.0
|
||||
// NewCodec creates a new RPC server codec with support for JSON-RPC 2.0 based
|
||||
// on explicitly given encoding and decoding methods.
|
||||
func NewCodec(rwc io.ReadWriteCloser, encode, decode func(v interface{}) error) ServerCodec {
|
||||
return &jsonCodec{
|
||||
closed: make(chan interface{}),
|
||||
encode: encode,
|
||||
decode: decode,
|
||||
rw: rwc,
|
||||
}
|
||||
}
|
||||
|
||||
// NewJSONCodec creates a new RPC server codec with support for JSON-RPC 2.0.
|
||||
func NewJSONCodec(rwc io.ReadWriteCloser) ServerCodec {
|
||||
d := json.NewDecoder(rwc)
|
||||
d.UseNumber()
|
||||
return &jsonCodec{closed: make(chan interface{}), d: d, e: json.NewEncoder(rwc), rw: rwc}
|
||||
enc := json.NewEncoder(rwc)
|
||||
dec := json.NewDecoder(rwc)
|
||||
dec.UseNumber()
|
||||
|
||||
return &jsonCodec{
|
||||
closed: make(chan interface{}),
|
||||
encode: enc.Encode,
|
||||
decode: dec.Decode,
|
||||
rw: rwc,
|
||||
}
|
||||
}
|
||||
|
||||
// isBatch returns true when the first non-whitespace characters is '['
|
||||
@@ -123,14 +141,12 @@ func (c *jsonCodec) ReadRequestHeaders() ([]rpcRequest, bool, Error) {
|
||||
defer c.decMu.Unlock()
|
||||
|
||||
var incomingMsg json.RawMessage
|
||||
if err := c.d.Decode(&incomingMsg); err != nil {
|
||||
if err := c.decode(&incomingMsg); err != nil {
|
||||
return nil, false, &invalidRequestError{err.Error()}
|
||||
}
|
||||
|
||||
if isBatch(incomingMsg) {
|
||||
return parseBatchRequest(incomingMsg)
|
||||
}
|
||||
|
||||
return parseRequest(incomingMsg)
|
||||
}
|
||||
|
||||
@@ -304,9 +320,6 @@ func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]
|
||||
|
||||
// CreateResponse will create a JSON-RPC success response with the given id and reply as result.
|
||||
func (c *jsonCodec) CreateResponse(id interface{}, reply interface{}) interface{} {
|
||||
if isHexNum(reflect.TypeOf(reply)) {
|
||||
return &jsonSuccessResponse{Version: jsonrpcVersion, Id: id, Result: fmt.Sprintf(`%#x`, reply)}
|
||||
}
|
||||
return &jsonSuccessResponse{Version: jsonrpcVersion, Id: id, Result: reply}
|
||||
}
|
||||
|
||||
@@ -324,11 +337,6 @@ func (c *jsonCodec) CreateErrorResponseWithInfo(id interface{}, err Error, info
|
||||
|
||||
// CreateNotification will create a JSON-RPC notification with the given subscription id and event as params.
|
||||
func (c *jsonCodec) CreateNotification(subid, namespace string, event interface{}) interface{} {
|
||||
if isHexNum(reflect.TypeOf(event)) {
|
||||
return &jsonNotification{Version: jsonrpcVersion, Method: namespace + notificationMethodSuffix,
|
||||
Params: jsonSubscription{Subscription: subid, Result: fmt.Sprintf(`%#x`, event)}}
|
||||
}
|
||||
|
||||
return &jsonNotification{Version: jsonrpcVersion, Method: namespace + notificationMethodSuffix,
|
||||
Params: jsonSubscription{Subscription: subid, Result: event}}
|
||||
}
|
||||
@@ -338,7 +346,7 @@ func (c *jsonCodec) Write(res interface{}) error {
|
||||
c.encMu.Lock()
|
||||
defer c.encMu.Unlock()
|
||||
|
||||
return c.e.Encode(res)
|
||||
return c.encode(res)
|
||||
}
|
||||
|
||||
// Close the underlying connection
|
||||
|
||||
+14
-17
@@ -25,8 +25,8 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"gopkg.in/fatih/set.v0"
|
||||
)
|
||||
|
||||
const MetadataApi = "rpc"
|
||||
@@ -46,7 +46,7 @@ const (
|
||||
func NewServer() *Server {
|
||||
server := &Server{
|
||||
services: make(serviceRegistry),
|
||||
codecs: set.New(),
|
||||
codecs: mapset.NewSet(),
|
||||
run: 1,
|
||||
}
|
||||
|
||||
@@ -94,11 +94,12 @@ func (s *Server) RegisterName(name string, rcvr interface{}) error {
|
||||
|
||||
methods, subscriptions := suitableCallbacks(rcvrVal, svc.typ)
|
||||
|
||||
// already a previous service register under given sname, merge methods/subscriptions
|
||||
if len(methods) == 0 && len(subscriptions) == 0 {
|
||||
return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr)
|
||||
}
|
||||
|
||||
// already a previous service register under given name, merge methods/subscriptions
|
||||
if regsvc, present := s.services[name]; present {
|
||||
if len(methods) == 0 && len(subscriptions) == 0 {
|
||||
return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr)
|
||||
}
|
||||
for _, m := range methods {
|
||||
regsvc.callbacks[formatName(m.method.Name)] = m
|
||||
}
|
||||
@@ -111,10 +112,6 @@ func (s *Server) RegisterName(name string, rcvr interface{}) error {
|
||||
svc.name = name
|
||||
svc.callbacks, svc.subscriptions = methods, subscriptions
|
||||
|
||||
if len(svc.callbacks) == 0 && len(svc.subscriptions) == 0 {
|
||||
return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr)
|
||||
}
|
||||
|
||||
s.services[svc.name] = svc
|
||||
return nil
|
||||
}
|
||||
@@ -125,7 +122,7 @@ func (s *Server) RegisterName(name string, rcvr interface{}) error {
|
||||
// If singleShot is true it will process a single request, otherwise it will handle
|
||||
// requests until the codec returns an error when reading a request (in most cases
|
||||
// an EOF). It executes requests in parallel when singleShot is false.
|
||||
func (s *Server) serveRequest(codec ServerCodec, singleShot bool, options CodecOption) error {
|
||||
func (s *Server) serveRequest(ctx context.Context, codec ServerCodec, singleShot bool, options CodecOption) error {
|
||||
var pend sync.WaitGroup
|
||||
|
||||
defer func() {
|
||||
@@ -140,11 +137,12 @@ func (s *Server) serveRequest(codec ServerCodec, singleShot bool, options CodecO
|
||||
s.codecsMu.Unlock()
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
// ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// if the codec supports notification include a notifier that callbacks can use
|
||||
// to send notification to clients. It is thight to the codec/connection. If the
|
||||
// to send notification to clients. It is tied to the codec/connection. If the
|
||||
// connection is closed the notifier will stop and cancels all active subscriptions.
|
||||
if options&OptionSubscriptions == OptionSubscriptions {
|
||||
ctx = context.WithValue(ctx, notifierKey{}, newNotifier(codec))
|
||||
@@ -215,14 +213,14 @@ func (s *Server) serveRequest(codec ServerCodec, singleShot bool, options CodecO
|
||||
// stopped. In either case the codec is closed.
|
||||
func (s *Server) ServeCodec(codec ServerCodec, options CodecOption) {
|
||||
defer codec.Close()
|
||||
s.serveRequest(codec, false, options)
|
||||
s.serveRequest(context.Background(), codec, false, options)
|
||||
}
|
||||
|
||||
// ServeSingleRequest reads and processes a single RPC request from the given codec. It will not
|
||||
// close the codec unless a non-recoverable error has occurred. Note, this method will return after
|
||||
// a single request has been processed!
|
||||
func (s *Server) ServeSingleRequest(codec ServerCodec, options CodecOption) {
|
||||
s.serveRequest(codec, true, options)
|
||||
func (s *Server) ServeSingleRequest(ctx context.Context, codec ServerCodec, options CodecOption) {
|
||||
s.serveRequest(ctx, codec, true, options)
|
||||
}
|
||||
|
||||
// Stop will stop reading new requests, wait for stopPendingRequestTimeout to allow pending requests to finish,
|
||||
@@ -312,7 +310,6 @@ func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverReque
|
||||
if len(reply) == 0 {
|
||||
return codec.CreateResponse(req.id, nil), nil
|
||||
}
|
||||
|
||||
if req.callb.errPos >= 0 { // test if method returned an error
|
||||
if !reply[req.callb.errPos].IsNil() {
|
||||
e := reply[req.callb.errPos].Interface().(error)
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"gopkg.in/fatih/set.v0"
|
||||
)
|
||||
|
||||
// API describes the set of methods offered over the RPC interface
|
||||
@@ -73,7 +73,7 @@ type Server struct {
|
||||
|
||||
run int32
|
||||
codecsMu sync.Mutex
|
||||
codecs *set.Set
|
||||
codecs mapset.Set
|
||||
}
|
||||
|
||||
// rpcRequest represents a raw incoming RPC request
|
||||
|
||||
-15
@@ -22,7 +22,6 @@ import (
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -105,20 +104,6 @@ func formatName(name string) string {
|
||||
return string(ret)
|
||||
}
|
||||
|
||||
var bigIntType = reflect.TypeOf((*big.Int)(nil)).Elem()
|
||||
|
||||
// Indication if this type should be serialized in hex
|
||||
func isHexNum(t reflect.Type) bool {
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
return t == bigIntType
|
||||
}
|
||||
|
||||
// suitableCallbacks iterates over the methods of the given type. It will determine if a method satisfies the criteria
|
||||
// for a RPC callback or a subscription callback and adds it to the collection of callbacks or subscriptions. See server
|
||||
// documentation for a summary of these criteria.
|
||||
|
||||
+34
-6
@@ -17,8 +17,10 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -27,11 +29,28 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"golang.org/x/net/websocket"
|
||||
"gopkg.in/fatih/set.v0"
|
||||
)
|
||||
|
||||
// websocketJSONCodec is a custom JSON codec with payload size enforcement and
|
||||
// special number parsing.
|
||||
var websocketJSONCodec = websocket.Codec{
|
||||
// Marshal is the stock JSON marshaller used by the websocket library too.
|
||||
Marshal: func(v interface{}) ([]byte, byte, error) {
|
||||
msg, err := json.Marshal(v)
|
||||
return msg, websocket.TextFrame, err
|
||||
},
|
||||
// Unmarshal is a specialized unmarshaller to properly convert numbers.
|
||||
Unmarshal: func(msg []byte, payloadType byte, v interface{}) error {
|
||||
dec := json.NewDecoder(bytes.NewReader(msg))
|
||||
dec.UseNumber()
|
||||
|
||||
return dec.Decode(v)
|
||||
},
|
||||
}
|
||||
|
||||
// WebsocketHandler returns a handler that serves JSON-RPC to WebSocket connections.
|
||||
//
|
||||
// allowedOrigins should be a comma-separated list of allowed origin URLs.
|
||||
@@ -40,7 +59,16 @@ func (srv *Server) WebsocketHandler(allowedOrigins []string) http.Handler {
|
||||
return websocket.Server{
|
||||
Handshake: wsHandshakeValidator(allowedOrigins),
|
||||
Handler: func(conn *websocket.Conn) {
|
||||
srv.ServeCodec(NewJSONCodec(conn), OptionMethodInvocation|OptionSubscriptions)
|
||||
// Create a custom encode/decode pair to enforce payload size and number encoding
|
||||
conn.MaxPayloadBytes = maxRequestContentLength
|
||||
|
||||
encoder := func(v interface{}) error {
|
||||
return websocketJSONCodec.Send(conn, v)
|
||||
}
|
||||
decoder := func(v interface{}) error {
|
||||
return websocketJSONCodec.Receive(conn, v)
|
||||
}
|
||||
srv.ServeCodec(NewCodec(conn, encoder, decoder), OptionMethodInvocation|OptionSubscriptions)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -56,7 +84,7 @@ func NewWSServer(allowedOrigins []string, srv *Server) *http.Server {
|
||||
// websocket upgrade process. When a '*' is specified as an allowed origins all
|
||||
// connections are accepted.
|
||||
func wsHandshakeValidator(allowedOrigins []string) func(*websocket.Config, *http.Request) error {
|
||||
origins := set.New()
|
||||
origins := mapset.NewSet()
|
||||
allowAllOrigins := false
|
||||
|
||||
for _, origin := range allowedOrigins {
|
||||
@@ -69,18 +97,18 @@ func wsHandshakeValidator(allowedOrigins []string) func(*websocket.Config, *http
|
||||
}
|
||||
|
||||
// allow localhost if no allowedOrigins are specified.
|
||||
if len(origins.List()) == 0 {
|
||||
if len(origins.ToSlice()) == 0 {
|
||||
origins.Add("http://localhost")
|
||||
if hostname, err := os.Hostname(); err == nil {
|
||||
origins.Add("http://" + strings.ToLower(hostname))
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug(fmt.Sprintf("Allowed origin(s) for WS RPC interface %v\n", origins.List()))
|
||||
log.Debug(fmt.Sprintf("Allowed origin(s) for WS RPC interface %v\n", origins.ToSlice()))
|
||||
|
||||
f := func(cfg *websocket.Config, req *http.Request) error {
|
||||
origin := strings.ToLower(req.Header.Get("Origin"))
|
||||
if allowAllOrigins || origins.Has(origin) {
|
||||
if allowAllOrigins || origins.Contains(origin) {
|
||||
return nil
|
||||
}
|
||||
log.Warn(fmt.Sprintf("origin '%s' not allowed on WS-RPC interface\n", origin))
|
||||
|
||||
Reference in New Issue
Block a user