good morning!!!!

Skip to content
Snippets Groups Projects
handler.go 10.5 KiB
Newer Older
a's avatar
rpc
a committed
// Copyright 2019 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 jrpc

import (
a's avatar
a committed
	"bytes"
a's avatar
rpc
a committed
	"context"
a's avatar
a committed
	"encoding/json"
	"errors"
	"reflect"
	"strings"
a's avatar
rpc
a committed
	"sync"
a's avatar
a committed
	"time"
a's avatar
rpc
a committed

a's avatar
a committed
	"tuxpa.in/a/zlog"
a's avatar
rpc
a committed
)

// handler handles JSON-RPC messages. There is one handler per connection. Note that
// handler is not safe for concurrent use. Message handling never blocks indefinitely
// because RPCs are processed on background goroutines launched by handler.
//
// The entry points for incoming messages are:
//
a's avatar
a committed
//	h.handleMsg(message)
//	h.handleBatch(message)
a's avatar
rpc
a committed
//
// Outgoing calls use the requestOp struct. Register the request before sending it
// on the connection:
//
a's avatar
a committed
//	op := &requestOp{ids: ...}
//	h.addRequestOp(op)
a's avatar
rpc
a committed
//
// Now send the request, then wait for the reply to be delivered through handleMsg:
//
a's avatar
a committed
//	if err := op.wait(...); err != nil {
//	    h.removeRequestOp(op) // timeout, etc.
//	}
a's avatar
rpc
a committed
type handler struct {
	reg        Router
	respWait   map[string]*requestOp // active client requests
	callWG     sync.WaitGroup        // pending call goroutines
	rootCtx    context.Context       // canceled by close()
	cancelRoot func()                // cancel function for rootCtx
a's avatar
a committed
	conn       JsonWriter            // where responses will be sent
a's avatar
rpc
a committed
	log        *zlog.Logger
a's avatar
a committed
	subLock       sync.RWMutex
	clientSubs    map[string]*ClientSubscription // active client subscriptions
	serverSubs    map[SubID]*Subscription
	unsubscribeCb *callback

a's avatar
rpc
a committed
}

type callProc struct {
a's avatar
a committed
	ctx       context.Context
	notifiers []*Notifier
a's avatar
rpc
a committed
}

a's avatar
a committed
func newHandler(connCtx context.Context, conn JsonWriter, reg Router) *handler {
a's avatar
rpc
a committed
	rootCtx, cancelRoot := context.WithCancel(connCtx)
	h := &handler{
a's avatar
rpc
a committed
		reg:        reg,
		conn:       conn,
		respWait:   make(map[string]*requestOp),
a's avatar
a committed
		clientSubs: map[string]*ClientSubscription{},
		serverSubs: map[SubID]*Subscription{},
a's avatar
rpc
a committed
		rootCtx:    rootCtx,
		cancelRoot: cancelRoot,
		log:        zlog.Ctx(connCtx),
	}
a's avatar
a committed
		cl := h.log.With().Str("conn", conn.RemoteAddr()).Logger()
a's avatar
rpc
a committed
		h.log = &cl
	}
a's avatar
a committed
	h.unsubscribeCb = newCallback(reflect.Value{}, reflect.ValueOf(h.unsubscribe))
a's avatar
rpc
a committed
	return h
}

// handleBatch executes all messages in a batch and returns the responses.
func (h *handler) handleBatch(msgs []*jsonrpcMessage) {
	// Emit error response for empty batches:
	if len(msgs) == 0 {
		h.startCallProc(func(cp *callProc) {
a's avatar
a committed
			h.conn.WriteJSON(cp.ctx, errorMessage(&invalidRequestError{"empty batch"}))
a's avatar
rpc
a committed
		})
		return
	}
	// Handle non-call messages first:
	calls := make([]*jsonrpcMessage, 0, len(msgs))
	for _, msg := range msgs {
		if handled := h.handleImmediate(msg); !handled {
			calls = append(calls, msg)
		}
	}
	if len(calls) == 0 {
		return
	}
	// Process calls on a goroutine because they may block indefinitely:
	h.startCallProc(func(cp *callProc) {
		answers := make([]*jsonrpcMessage, 0, len(msgs))
		for _, msg := range calls {
a's avatar
a committed
			r := NewMsgRequest(cp.ctx, h.peer, *msg)
			if answer := h.handleCallMsg(cp, r); answer != nil {
a's avatar
a committed
				answers = append(answers, answer.Msg())
a's avatar
rpc
a committed
			}
		}
a's avatar
a committed
		h.addSubscriptions(cp.notifiers)
a's avatar
rpc
a committed
		if len(answers) > 0 {
a's avatar
a committed
			h.conn.WriteJSON(cp.ctx, answers)
a's avatar
rpc
a committed
		}
a's avatar
a committed
		for _, n := range cp.notifiers {
			n.activate()
		}
a's avatar
rpc
a committed
	})
}

// handleMsg handles a single message.
func (h *handler) handleMsg(msg *jsonrpcMessage) {
	if ok := h.handleImmediate(msg); ok {
		return
	}
	h.startCallProc(func(cp *callProc) {
a's avatar
a committed
		r := NewMsgRequest(cp.ctx, h.peer, *msg)
		answer := h.handleCallMsg(cp, r)
a's avatar
a committed
		h.addSubscriptions(cp.notifiers)
a's avatar
rpc
a committed
		if answer != nil {
a's avatar
a committed
			h.conn.WriteJSON(cp.ctx, answer)
a's avatar
rpc
a committed
		}
a's avatar
a committed
		for _, n := range cp.notifiers {
			n.activate()
		}
a's avatar
rpc
a committed
	})
}

// close cancels all requests except for inflightReq and waits for
// call goroutines to shut down.
func (h *handler) close(err error, inflightReq *requestOp) {
	h.cancelAllRequests(err, inflightReq)
	h.callWG.Wait()
	h.cancelRoot()
a's avatar
a committed
	h.cancelServerSubscriptions(err)
a's avatar
rpc
a committed
}

// addRequestOp registers a request operation.
func (h *handler) addRequestOp(op *requestOp) {
	for _, id := range op.ids {
		h.respWait[string(id)] = op
	}
}

// removeRequestOps stops waiting for the given request IDs.
func (h *handler) removeRequestOp(op *requestOp) {
	for _, id := range op.ids {
		delete(h.respWait, string(id))
	}
}

// cancelAllRequests unblocks and removes pending requests and active subscriptions.
func (h *handler) cancelAllRequests(err error, inflightReq *requestOp) {
	didClose := make(map[*requestOp]bool)
	if inflightReq != nil {
		didClose[inflightReq] = true
	}

	for id, op := range h.respWait {
		// Remove the op so that later calls will not close op.resp again.
		delete(h.respWait, id)
		if !didClose[op] {
			op.err = err
			close(op.resp)
			didClose[op] = true
		}
	}
}

// startCallProc runs fn in a new goroutine and starts tracking it in the h.calls wait group.
func (h *handler) startCallProc(fn func(*callProc)) {
	h.callWG.Add(1)
	go func() {
		ctx, cancel := context.WithCancel(h.rootCtx)
		defer h.callWG.Done()
		defer cancel()
		fn(&callProc{ctx: ctx})
	}()
}

// handleImmediate executes non-call messages. It returns false if the message is a
// call or requires a reply.
func (h *handler) handleImmediate(msg *jsonrpcMessage) bool {
a's avatar
a committed
	start := time.Now()
a's avatar
rpc
a committed
	switch {
	case msg.isNotification():
a's avatar
a committed
		if strings.HasSuffix(msg.Method, notificationMethodSuffix) {
			h.handleSubscriptionResult(msg)
			return true
		}
		return false
a's avatar
rpc
a committed
	case msg.isResponse():
a's avatar
a committed
		h.handleResponse(msg.toResponse())
a's avatar
a committed
		h.log.Trace().Str("reqid", string(msg.ID.RawMessage())).Dur("duration", time.Since(start)).Msg("Handled RPC response")
a's avatar
rpc
a committed
		return true
	default:
		return false
	}
}

a's avatar
a committed
func (h *handler) handleSubscriptionResult(msg *jsonrpcMessage) {
	var result subscriptionResult
	if err := json.Unmarshal(msg.Params, &result); err != nil {
		h.log.Trace().Msg("Dropping invalid subscription message")
		return
	}
	if h.clientSubs[result.ID] != nil {
		h.clientSubs[result.ID].deliver(result.Result)
	}
}

a's avatar
a committed
func (h *handler) handleResponse(msg *Response) {
a's avatar
a committed
	op := h.respWait[string(msg.ID.RawMessage())]
a's avatar
rpc
a committed
	if op == nil {
a's avatar
a committed
		h.log.Debug().Str("reqid", string(msg.ID.RawMessage())).Msg("Unsolicited RPC response")
a's avatar
rpc
a committed
		return
	}
a's avatar
a committed
	delete(h.respWait, string(msg.ID.RawMessage()))
a's avatar
a committed
	if op.sub == nil {
a's avatar
a committed
		// not a sub, so just send the msg back
a's avatar
a committed
		op.resp <- msg.Msg()
a's avatar
a committed
		return
	}
	// For subscription responses, start the subscription if the server
	// indicates success. EthSubscribe gets unblocked in either case through
	// the op.resp channel.
	defer close(op.resp)
	if msg.Error != nil {
		op.err = msg.Error
		return
	}
	if op.err = json.Unmarshal(msg.Result, &op.sub.subid); op.err == nil {
		go op.sub.start()
		h.clientSubs[op.sub.subid] = op.sub
	}
a's avatar
rpc
a committed
}

// handleCallMsg executes a call message and returns the answer.
a's avatar
a committed
// TODO: export prometheus metrics maybe? also fix logging
a's avatar
a committed
func (h *handler) handleCallMsg(ctx *callProc, r *Request) *Response {
a's avatar
rpc
a committed
	switch {
a's avatar
a committed
	case r.isNotification():
		go h.handleCall(ctx, r)
a's avatar
rpc
a committed
		return nil
a's avatar
a committed
	case r.isCall():
		resp := h.handleCall(ctx, r)
a's avatar
rpc
a committed
		return resp
a's avatar
a committed
	case r.hasValidID():
a's avatar
a committed
		return r.errorResponse(&invalidRequestError{"invalid request"})
a's avatar
rpc
a committed
	default:
a's avatar
a committed
		res := r.errorResponse(&invalidRequestError{"invalid request"})
a's avatar
a committed
		res.ID = NewNullIDPtr()
a's avatar
a committed
		return res
a's avatar
rpc
a committed
	}
}

a's avatar
a committed
func (h *handler) handleCall(cp *callProc, r *Request) *Response {
a's avatar
a committed
	callb := h.reg.Match(NewRouteContext(), r.Method)
	mw := NewReaderResponseWriterMsg(r)
	if r.isSubscribe() {
		return h.handleSubscribe(cp, r)
a's avatar
a committed
	}
a's avatar
a committed
	if r.isUnsubscribe() {
		h.unsubscribeCb.ServeRPC(mw, r)
a's avatar
a committed
		return mw.Response()
a's avatar
a committed
	}
a's avatar
a committed
	// no method found
	if !callb {
a's avatar
a committed
		mw.Send(nil, &methodNotFoundError{method: r.Method})
		return mw.Response()
a's avatar
a committed
	}
	// now actually run the handler
	h.reg.ServeRPC(mw, r)
a's avatar
a committed
	return mw.Response()
a's avatar
a committed
}

// handleSubscribe processes *_subscribe method calls.
a's avatar
a committed
func (h *handler) handleSubscribe(cp *callProc, r *Request) *Response {
	mw := NewReaderResponseWriterMsg(r.WithContext(cp.ctx))
a's avatar
a committed
	switch h.peer.Transport {
	case "http", "https":
a's avatar
a committed
		mw.Send(nil, ErrNotificationsUnsupported)
		return mw.Response()
a's avatar
a committed
	}

	// Subscription method name is first argument.
a's avatar
a committed
	name, err := parseSubscriptionName(r.Params)
a's avatar
a committed
	if err != nil {
a's avatar
a committed
		mw.Send(nil, &invalidParamsError{err.Error()})
		return mw.Response()
a's avatar
a committed
	}
a's avatar
a committed
	namespace := r.namespace()
	has := h.reg.Match(NewRouteContext(), r.Method)
a's avatar
a committed
	if !has {
a's avatar
a committed
		mw.Send(nil, &subscriptionNotFoundError{namespace, name})
		return mw.Response()
a's avatar
a committed
	}
	// Install notifier in context so the subscription handler can find it.
	n := &Notifier{h: h, namespace: namespace, idgen: randomIDGenerator()}
	cp.notifiers = append(cp.notifiers, n)
a's avatar
a committed
	req := r.WithContext(cp.ctx)
a's avatar
a committed
	// now actually run the handler
	req = req.WithContext(
		context.WithValue(req.ctx, notifierKey{}, n),
	)
	h.reg.ServeRPC(mw, req)
a's avatar
a committed
	return mw.Response()
a's avatar
a committed
}
a's avatar
a committed

a's avatar
a committed
// parseSubscriptionName extracts the subscription name from an encoded argument array.
func parseSubscriptionName(rawArgs json.RawMessage) (string, error) {
	dec := json.NewDecoder(bytes.NewReader(rawArgs))
	if tok, _ := dec.Token(); tok != json.Delim('[') {
		return "", errors.New("non-array args")
	}
	v, _ := dec.Token()
	method, ok := v.(string)
	if !ok {
		return "", errors.New("expected subscription name as first argument")
	}
	return method, nil
a's avatar
a committed
}

func (h *handler) unsubscribe(ctx context.Context, id SubID) (bool, error) {
	h.subLock.Lock()
	defer h.subLock.Unlock()

	s := h.serverSubs[id]
	if s == nil {
		return false, ErrSubscriptionNotFound
	}
	close(s.err)
	delete(h.serverSubs, id)
	return true, nil
}

// cancelServerSubscriptions removes all subscriptions and closes their error channels.
func (h *handler) cancelServerSubscriptions(err error) {
	h.subLock.Lock()
	defer h.subLock.Unlock()

	for id, s := range h.serverSubs {
		s.err <- err
		close(s.err)
		delete(h.serverSubs, id)
	}
}

func (h *handler) addSubscriptions(nn []*Notifier) {
	h.subLock.Lock()
	defer h.subLock.Unlock()

	for _, n := range nn {
		if sub := n.takeSubscription(); sub != nil {
			h.serverSubs[sub.ID] = sub
		}
	}
}