github.com/core-coin/go-core/v2@v2.1.9/rpc/handler.go (about)

     1  // Copyright 2019 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-core library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package rpc
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"reflect"
    23  	"strconv"
    24  	"strings"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/core-coin/go-core/v2/log"
    29  )
    30  
    31  // handler handles JSON-RPC messages. There is one handler per connection. Note that
    32  // handler is not safe for concurrent use. Message handling never blocks indefinitely
    33  // because RPCs are processed on background goroutines launched by handler.
    34  //
    35  // The entry points for incoming messages are:
    36  //
    37  //	h.handleMsg(message)
    38  //	h.handleBatch(message)
    39  //
    40  // Outgoing calls use the requestOp struct. Register the request before sending it
    41  // on the connection:
    42  //
    43  //	op := &requestOp{ids: ...}
    44  //	h.addRequestOp(op)
    45  //
    46  // Now send the request, then wait for the reply to be delivered through handleMsg:
    47  //
    48  //	if err := op.wait(...); err != nil {
    49  //	    h.removeRequestOp(op) // timeout, etc.
    50  //	}
    51  type handler struct {
    52  	reg            *serviceRegistry
    53  	unsubscribeCb  *callback
    54  	idgen          func() ID                      // subscription ID generator
    55  	respWait       map[string]*requestOp          // active client requests
    56  	clientSubs     map[string]*ClientSubscription // active client subscriptions
    57  	callWG         sync.WaitGroup                 // pending call goroutines
    58  	rootCtx        context.Context                // canceled by close()
    59  	cancelRoot     func()                         // cancel function for rootCtx
    60  	conn           jsonWriter                     // where responses will be sent
    61  	log            log.Logger
    62  	allowSubscribe bool
    63  
    64  	subLock    sync.Mutex
    65  	serverSubs map[ID]*Subscription
    66  }
    67  
    68  type callProc struct {
    69  	ctx       context.Context
    70  	notifiers []*Notifier
    71  }
    72  
    73  func newHandler(connCtx context.Context, conn jsonWriter, idgen func() ID, reg *serviceRegistry) *handler {
    74  	rootCtx, cancelRoot := context.WithCancel(connCtx)
    75  	h := &handler{
    76  		reg:            reg,
    77  		idgen:          idgen,
    78  		conn:           conn,
    79  		respWait:       make(map[string]*requestOp),
    80  		clientSubs:     make(map[string]*ClientSubscription),
    81  		rootCtx:        rootCtx,
    82  		cancelRoot:     cancelRoot,
    83  		allowSubscribe: true,
    84  		serverSubs:     make(map[ID]*Subscription),
    85  		log:            log.Root(),
    86  	}
    87  	if conn.remoteAddr() != "" {
    88  		h.log = h.log.New("conn", conn.remoteAddr())
    89  	}
    90  	h.unsubscribeCb = newCallback(reflect.Value{}, reflect.ValueOf(h.unsubscribe))
    91  	return h
    92  }
    93  
    94  // handleBatch executes all messages in a batch and returns the responses.
    95  func (h *handler) handleBatch(msgs []*jsonrpcMessage) {
    96  	// Emit error response for empty batches:
    97  	if len(msgs) == 0 {
    98  		h.startCallProc(func(cp *callProc) {
    99  			h.conn.writeJSON(cp.ctx, errorMessage(&invalidRequestError{"empty batch"}))
   100  		})
   101  		return
   102  	}
   103  
   104  	// Handle non-call messages first:
   105  	calls := make([]*jsonrpcMessage, 0, len(msgs))
   106  	for _, msg := range msgs {
   107  		if handled := h.handleImmediate(msg); !handled {
   108  			calls = append(calls, msg)
   109  		}
   110  	}
   111  	if len(calls) == 0 {
   112  		return
   113  	}
   114  	// Process calls on a goroutine because they may block indefinitely:
   115  	h.startCallProc(func(cp *callProc) {
   116  		answers := make([]*jsonrpcMessage, 0, len(msgs))
   117  		for _, msg := range calls {
   118  			if answer := h.handleCallMsg(cp, msg); answer != nil {
   119  				answers = append(answers, answer)
   120  			}
   121  		}
   122  		h.addSubscriptions(cp.notifiers)
   123  		if len(answers) > 0 {
   124  			h.conn.writeJSON(cp.ctx, answers)
   125  		}
   126  		for _, n := range cp.notifiers {
   127  			n.activate()
   128  		}
   129  	})
   130  }
   131  
   132  // handleMsg handles a single message.
   133  func (h *handler) handleMsg(msg *jsonrpcMessage) {
   134  	if ok := h.handleImmediate(msg); ok {
   135  		return
   136  	}
   137  	h.startCallProc(func(cp *callProc) {
   138  		answer := h.handleCallMsg(cp, msg)
   139  		h.addSubscriptions(cp.notifiers)
   140  		if answer != nil {
   141  			h.conn.writeJSON(cp.ctx, answer)
   142  		}
   143  		for _, n := range cp.notifiers {
   144  			n.activate()
   145  		}
   146  	})
   147  }
   148  
   149  // close cancels all requests except for inflightReq and waits for
   150  // call goroutines to shut down.
   151  func (h *handler) close(err error, inflightReq *requestOp) {
   152  	h.cancelAllRequests(err, inflightReq)
   153  	h.callWG.Wait()
   154  	h.cancelRoot()
   155  	h.cancelServerSubscriptions(err)
   156  }
   157  
   158  // addRequestOp registers a request operation.
   159  func (h *handler) addRequestOp(op *requestOp) {
   160  	for _, id := range op.ids {
   161  		h.respWait[string(id)] = op
   162  	}
   163  }
   164  
   165  // removeRequestOps stops waiting for the given request IDs.
   166  func (h *handler) removeRequestOp(op *requestOp) {
   167  	for _, id := range op.ids {
   168  		delete(h.respWait, string(id))
   169  	}
   170  }
   171  
   172  // cancelAllRequests unblocks and removes pending requests and active subscriptions.
   173  func (h *handler) cancelAllRequests(err error, inflightReq *requestOp) {
   174  	didClose := make(map[*requestOp]bool)
   175  	if inflightReq != nil {
   176  		didClose[inflightReq] = true
   177  	}
   178  
   179  	for id, op := range h.respWait {
   180  		// Remove the op so that later calls will not close op.resp again.
   181  		delete(h.respWait, id)
   182  
   183  		if !didClose[op] {
   184  			op.err = err
   185  			close(op.resp)
   186  			didClose[op] = true
   187  		}
   188  	}
   189  	for id, sub := range h.clientSubs {
   190  		delete(h.clientSubs, id)
   191  		sub.quitWithError(false, err)
   192  	}
   193  }
   194  
   195  func (h *handler) addSubscriptions(nn []*Notifier) {
   196  	h.subLock.Lock()
   197  	defer h.subLock.Unlock()
   198  
   199  	for _, n := range nn {
   200  		if sub := n.takeSubscription(); sub != nil {
   201  			h.serverSubs[sub.ID] = sub
   202  		}
   203  	}
   204  }
   205  
   206  // cancelServerSubscriptions removes all subscriptions and closes their error channels.
   207  func (h *handler) cancelServerSubscriptions(err error) {
   208  	h.subLock.Lock()
   209  	defer h.subLock.Unlock()
   210  
   211  	for id, s := range h.serverSubs {
   212  		s.err <- err
   213  		close(s.err)
   214  		delete(h.serverSubs, id)
   215  	}
   216  }
   217  
   218  // startCallProc runs fn in a new goroutine and starts tracking it in the h.calls wait group.
   219  func (h *handler) startCallProc(fn func(*callProc)) {
   220  	h.callWG.Add(1)
   221  	go func() {
   222  		ctx, cancel := context.WithCancel(h.rootCtx)
   223  		defer h.callWG.Done()
   224  		defer cancel()
   225  		fn(&callProc{ctx: ctx})
   226  	}()
   227  }
   228  
   229  // handleImmediate executes non-call messages. It returns false if the message is a
   230  // call or requires a reply.
   231  func (h *handler) handleImmediate(msg *jsonrpcMessage) bool {
   232  	start := time.Now()
   233  	switch {
   234  	case msg.isNotification():
   235  		if strings.HasSuffix(msg.Method, notificationMethodSuffix) {
   236  			h.handleSubscriptionResult(msg)
   237  			return true
   238  		}
   239  		return false
   240  	case msg.isResponse():
   241  		h.handleResponse(msg)
   242  		h.log.Trace("Handled RPC response", "reqid", idForLog{msg.ID}, "t", time.Since(start))
   243  		return true
   244  	default:
   245  		return false
   246  	}
   247  }
   248  
   249  // handleSubscriptionResult processes subscription notifications.
   250  func (h *handler) handleSubscriptionResult(msg *jsonrpcMessage) {
   251  	var result subscriptionResult
   252  	if err := json.Unmarshal(msg.Params, &result); err != nil {
   253  		h.log.Debug("Dropping invalid subscription message")
   254  		return
   255  	}
   256  	if h.clientSubs[result.ID] != nil {
   257  		h.clientSubs[result.ID].deliver(result.Result)
   258  	}
   259  }
   260  
   261  // handleResponse processes method call responses.
   262  func (h *handler) handleResponse(msg *jsonrpcMessage) {
   263  	op := h.respWait[string(msg.ID)]
   264  	if op == nil {
   265  		h.log.Debug("Unsolicited RPC response", "reqid", idForLog{msg.ID})
   266  		return
   267  	}
   268  	delete(h.respWait, string(msg.ID))
   269  	// For normal responses, just forward the reply to Call/BatchCall.
   270  	if op.sub == nil {
   271  		op.resp <- msg
   272  		return
   273  	}
   274  	// For subscription responses, start the subscription if the server
   275  	// indicates success. XcbSubscribe gets unblocked in either case through
   276  	// the op.resp channel.
   277  	defer close(op.resp)
   278  	if msg.Error != nil {
   279  		op.err = msg.Error
   280  		return
   281  	}
   282  	if op.err = json.Unmarshal(msg.Result, &op.sub.subid); op.err == nil {
   283  		go op.sub.start()
   284  		h.clientSubs[op.sub.subid] = op.sub
   285  	}
   286  }
   287  
   288  // handleCallMsg executes a call message and returns the answer.
   289  func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMessage {
   290  	start := time.Now()
   291  	switch {
   292  	case msg.isNotification():
   293  		h.handleCall(ctx, msg)
   294  		h.log.Debug("Served "+msg.Method, "t", time.Since(start))
   295  		return nil
   296  	case msg.isCall():
   297  		resp := h.handleCall(ctx, msg)
   298  		var ctx []interface{}
   299  		ctx = append(ctx, "reqid", idForLog{msg.ID}, "t", time.Since(start))
   300  		if resp.Error != nil {
   301  			ctx = append(ctx, "err", resp.Error.Message)
   302  			if resp.Error.Data != nil {
   303  				ctx = append(ctx, "errdata", resp.Error.Data)
   304  			}
   305  			h.log.Warn("Served "+msg.Method, ctx...)
   306  		} else {
   307  			h.log.Debug("Served "+msg.Method, ctx...)
   308  		}
   309  		return resp
   310  	case msg.hasValidID():
   311  		return msg.errorResponse(&invalidRequestError{"invalid request"})
   312  	default:
   313  		return errorMessage(&invalidRequestError{"invalid request"})
   314  	}
   315  }
   316  
   317  // handleCall processes method calls.
   318  func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage {
   319  	if msg.isSubscribe() {
   320  		return h.handleSubscribe(cp, msg)
   321  	}
   322  	var callb *callback
   323  	if msg.isUnsubscribe() {
   324  		callb = h.unsubscribeCb
   325  	} else {
   326  		callb = h.reg.callback(msg.Method)
   327  	}
   328  	if callb == nil {
   329  		return msg.errorResponse(&methodNotFoundError{method: msg.Method})
   330  	}
   331  	args, err := parsePositionalArguments(msg.Params, callb.argTypes)
   332  	if err != nil {
   333  		return msg.errorResponse(&invalidParamsError{err.Error()})
   334  	}
   335  	start := time.Now()
   336  	answer := h.runMethod(cp.ctx, msg, callb, args)
   337  
   338  	// Collect the statistics for RPC calls if metrics is enabled.
   339  	// We only care about pure rpc call. Filter out subscription.
   340  	if callb != h.unsubscribeCb {
   341  		rpcRequestGauge.Inc(1)
   342  		if answer.Error != nil {
   343  			failedReqeustGauge.Inc(1)
   344  		} else {
   345  			successfulRequestGauge.Inc(1)
   346  		}
   347  		rpcServingTimer.UpdateSince(start)
   348  		newRPCServingTimer(msg.Method, answer.Error == nil).UpdateSince(start)
   349  	}
   350  	return answer
   351  }
   352  
   353  // handleSubscribe processes *_subscribe method calls.
   354  func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage {
   355  	if !h.allowSubscribe {
   356  		return msg.errorResponse(ErrNotificationsUnsupported)
   357  	}
   358  
   359  	// Subscription method name is first argument.
   360  	name, err := parseSubscriptionName(msg.Params)
   361  	if err != nil {
   362  		return msg.errorResponse(&invalidParamsError{err.Error()})
   363  	}
   364  	namespace := msg.namespace()
   365  	callb := h.reg.subscription(namespace, name)
   366  	if callb == nil {
   367  		return msg.errorResponse(&subscriptionNotFoundError{namespace, name})
   368  	}
   369  
   370  	// Parse subscription name arg too, but remove it before calling the callback.
   371  	argTypes := append([]reflect.Type{stringType}, callb.argTypes...)
   372  	args, err := parsePositionalArguments(msg.Params, argTypes)
   373  	if err != nil {
   374  		return msg.errorResponse(&invalidParamsError{err.Error()})
   375  	}
   376  	args = args[1:]
   377  
   378  	// Install notifier in context so the subscription handler can find it.
   379  	n := &Notifier{h: h, namespace: namespace}
   380  	cp.notifiers = append(cp.notifiers, n)
   381  	ctx := context.WithValue(cp.ctx, notifierKey{}, n)
   382  
   383  	return h.runMethod(ctx, msg, callb, args)
   384  }
   385  
   386  // runMethod runs the Go callback for an RPC method.
   387  func (h *handler) runMethod(ctx context.Context, msg *jsonrpcMessage, callb *callback, args []reflect.Value) *jsonrpcMessage {
   388  	result, err := callb.call(ctx, msg.Method, args)
   389  	if err != nil {
   390  		return msg.errorResponse(err)
   391  	}
   392  	return msg.response(result)
   393  }
   394  
   395  // unsubscribe is the callback function for all *_unsubscribe calls.
   396  func (h *handler) unsubscribe(ctx context.Context, id ID) (bool, error) {
   397  	h.subLock.Lock()
   398  	defer h.subLock.Unlock()
   399  
   400  	s := h.serverSubs[id]
   401  	if s == nil {
   402  		return false, ErrSubscriptionNotFound
   403  	}
   404  	close(s.err)
   405  	delete(h.serverSubs, id)
   406  	return true, nil
   407  }
   408  
   409  type idForLog struct{ json.RawMessage }
   410  
   411  func (id idForLog) String() string {
   412  	if s, err := strconv.Unquote(string(id.RawMessage)); err == nil {
   413  		return s
   414  	}
   415  	return string(id.RawMessage)
   416  }