@ -49,17 +49,19 @@ import (
// h.removeRequestOp(op) // timeout, etc.
// h.removeRequestOp(op) // timeout, etc.
// }
// }
type handler struct {
type handler struct {
reg * serviceRegistry
reg * serviceRegistry
unsubscribeCb * callback
unsubscribeCb * callback
idgen func ( ) ID // subscription ID generator
idgen func ( ) ID // subscription ID generator
respWait map [ string ] * requestOp // active client requests
respWait map [ string ] * requestOp // active client requests
clientSubs map [ string ] * ClientSubscription // active client subscriptions
clientSubs map [ string ] * ClientSubscription // active client subscriptions
callWG sync . WaitGroup // pending call goroutines
callWG sync . WaitGroup // pending call goroutines
rootCtx context . Context // canceled by close()
rootCtx context . Context // canceled by close()
cancelRoot func ( ) // cancel function for rootCtx
cancelRoot func ( ) // cancel function for rootCtx
conn jsonWriter // where responses will be sent
conn jsonWriter // where responses will be sent
log log . Logger
log log . Logger
allowSubscribe bool
allowSubscribe bool
batchRequestLimit int
batchResponseMaxSize int
subLock sync . Mutex
subLock sync . Mutex
serverSubs map [ ID ] * Subscription
serverSubs map [ ID ] * Subscription
@ -70,19 +72,21 @@ type callProc struct {
notifiers [ ] * Notifier
notifiers [ ] * Notifier
}
}
func newHandler ( connCtx context . Context , conn jsonWriter , idgen func ( ) ID , reg * serviceRegistry ) * handler {
func newHandler ( connCtx context . Context , conn jsonWriter , idgen func ( ) ID , reg * serviceRegistry , batchRequestLimit , batchResponseMaxSize int ) * handler {
rootCtx , cancelRoot := context . WithCancel ( connCtx )
rootCtx , cancelRoot := context . WithCancel ( connCtx )
h := & handler {
h := & handler {
reg : reg ,
reg : reg ,
idgen : idgen ,
idgen : idgen ,
conn : conn ,
conn : conn ,
respWait : make ( map [ string ] * requestOp ) ,
respWait : make ( map [ string ] * requestOp ) ,
clientSubs : make ( map [ string ] * ClientSubscription ) ,
clientSubs : make ( map [ string ] * ClientSubscription ) ,
rootCtx : rootCtx ,
rootCtx : rootCtx ,
cancelRoot : cancelRoot ,
cancelRoot : cancelRoot ,
allowSubscribe : true ,
allowSubscribe : true ,
serverSubs : make ( map [ ID ] * Subscription ) ,
serverSubs : make ( map [ ID ] * Subscription ) ,
log : log . Root ( ) ,
log : log . Root ( ) ,
batchRequestLimit : batchRequestLimit ,
batchResponseMaxSize : batchResponseMaxSize ,
}
}
if conn . remoteAddr ( ) != "" {
if conn . remoteAddr ( ) != "" {
h . log = h . log . New ( "conn" , conn . remoteAddr ( ) )
h . log = h . log . New ( "conn" , conn . remoteAddr ( ) )
@ -134,16 +138,15 @@ func (b *batchCallBuffer) write(ctx context.Context, conn jsonWriter) {
b . doWrite ( ctx , conn , false )
b . doWrite ( ctx , conn , false )
}
}
// timeout sends the responses added so far. For the remaining unanswered call
// respondWithError sends the responses added so far. For the remaining unanswered call
// messages, it sends a timeout error response .
// messages, it responds with the given error .
func ( b * batchCallBuffer ) timeout ( ctx context . Context , conn jsonWriter ) {
func ( b * batchCallBuffer ) respondWithError ( ctx context . Context , conn jsonWriter , err erro r ) {
b . mutex . Lock ( )
b . mutex . Lock ( )
defer b . mutex . Unlock ( )
defer b . mutex . Unlock ( )
for _ , msg := range b . calls {
for _ , msg := range b . calls {
if ! msg . isNotification ( ) {
if ! msg . isNotification ( ) {
resp := msg . errorResponse ( & internalServerError { errcodeTimeout , errMsgTimeout } )
b . resp = append ( b . resp , msg . errorResponse ( err ) )
b . resp = append ( b . resp , resp )
}
}
}
}
b . doWrite ( ctx , conn , true )
b . doWrite ( ctx , conn , true )
@ -171,17 +174,24 @@ func (h *handler) handleBatch(msgs []*jsonrpcMessage) {
} )
} )
return
return
}
}
// Apply limit on total number of requests.
if h . batchRequestLimit != 0 && len ( msgs ) > h . batchRequestLimit {
h . startCallProc ( func ( cp * callProc ) {
h . respondWithBatchTooLarge ( cp , msgs )
} )
return
}
// Handle non-call messages first:
// Handle non-call messages first.
// Here we need to find the requestOp that sent the request batch.
calls := make ( [ ] * jsonrpcMessage , 0 , len ( msgs ) )
calls := make ( [ ] * jsonrpcMessage , 0 , len ( msgs ) )
for _ , msg := range msgs {
h . handleResponses ( msgs , func ( msg * jsonrpcMessage ) {
if handled := h . handleImmediate ( msg ) ; ! handled {
calls = append ( calls , msg )
calls = append ( calls , msg )
} )
}
}
if len ( calls ) == 0 {
if len ( calls ) == 0 {
return
return
}
}
// Process calls on a goroutine because they may block indefinitely:
// Process calls on a goroutine because they may block indefinitely:
h . startCallProc ( func ( cp * callProc ) {
h . startCallProc ( func ( cp * callProc ) {
var (
var (
@ -199,10 +209,12 @@ func (h *handler) handleBatch(msgs []*jsonrpcMessage) {
if timeout , ok := ContextRequestTimeout ( cp . ctx ) ; ok {
if timeout , ok := ContextRequestTimeout ( cp . ctx ) ; ok {
timer = time . AfterFunc ( timeout , func ( ) {
timer = time . AfterFunc ( timeout , func ( ) {
cancel ( )
cancel ( )
callBuffer . timeout ( cp . ctx , h . conn )
err := & internalServerError { errcodeTimeout , errMsgTimeout }
callBuffer . respondWithError ( cp . ctx , h . conn , err )
} )
} )
}
}
responseBytes := 0
for {
for {
// No need to handle rest of calls if timed out.
// No need to handle rest of calls if timed out.
if cp . ctx . Err ( ) != nil {
if cp . ctx . Err ( ) != nil {
@ -214,59 +226,86 @@ func (h *handler) handleBatch(msgs []*jsonrpcMessage) {
}
}
resp := h . handleCallMsg ( cp , msg )
resp := h . handleCallMsg ( cp , msg )
callBuffer . pushResponse ( resp )
callBuffer . pushResponse ( resp )
if resp != nil && h . batchResponseMaxSize != 0 {
responseBytes += len ( resp . Result )
if responseBytes > h . batchResponseMaxSize {
err := & internalServerError { errcodeResponseTooLarge , errMsgResponseTooLarge }
callBuffer . respondWithError ( cp . ctx , h . conn , err )
break
}
}
}
}
if timer != nil {
if timer != nil {
timer . Stop ( )
timer . Stop ( )
}
}
callBuffer . write ( cp . ctx , h . conn )
h . addSubscriptions ( cp . notifiers )
h . addSubscriptions ( cp . notifiers )
callBuffer . write ( cp . ctx , h . conn )
for _ , n := range cp . notifiers {
for _ , n := range cp . notifiers {
n . activate ( )
n . activate ( )
}
}
} )
} )
}
}
// handleMsg handles a single message.
func ( h * handler ) respondWithBatchTooLarge ( cp * callProc , batch [ ] * jsonrpcMessage ) {
func ( h * handler ) handleMsg ( msg * jsonrpcMessage ) {
resp := errorMessage ( & invalidRequestError { errMsgBatchTooLarge } )
if ok := h . handleImmediate ( msg ) ; ok {
// Find the first call and add its "id" field to the error.
return
// This is the best we can do, given that the protocol doesn't have a way
// of reporting an error for the entire batch.
for _ , msg := range batch {
if msg . isCall ( ) {
resp . ID = msg . ID
break
}
}
}
h . startCallProc ( func ( cp * callProc ) {
h . conn . writeJSON ( cp . ctx , [ ] * jsonrpcMessage { resp } , true )
var (
}
responded sync . Once
timer * time . Timer
cancel context . CancelFunc
)
cp . ctx , cancel = context . WithCancel ( cp . ctx )
defer cancel ( )
// Cancel the request context after timeout and send an error response. Since the
// handleMsg handles a single non-batch message.
// running method might not return immediately on timeout, we must wait for the
func ( h * handler ) handleMsg ( msg * jsonrpcMessage ) {
// timeout concurrently with processing the request.
msgs := [ ] * jsonrpcMessage { msg }
if timeout , ok := ContextRequestTimeout ( cp . ctx ) ; ok {
h . handleResponses ( msgs , func ( msg * jsonrpcMessage ) {
timer = time . AfterFunc ( timeout , func ( ) {
h . startCallProc ( func ( cp * callProc ) {
cancel ( )
h . handleNonBatchCall ( cp , msg )
responded . Do ( func ( ) {
} )
resp := msg . errorResponse ( & internalServerError { errcodeTimeout , errMsgTimeout } )
} )
h . conn . writeJSON ( cp . ctx , resp , true )
}
} )
} )
}
answer := h . handleCallMsg ( cp , msg )
func ( h * handler ) handleNonBatchCall ( cp * callProc , msg * jsonrpcMessage ) {
if timer != nil {
var (
timer . Stop ( )
responded sync . Once
}
timer * time . Timer
h . addSubscriptions ( cp . notifiers )
cancel context . CancelFunc
if answer != nil {
)
cp . ctx , cancel = context . WithCancel ( cp . ctx )
defer cancel ( )
// Cancel the request context after timeout and send an error response. Since the
// running method might not return immediately on timeout, we must wait for the
// timeout concurrently with processing the request.
if timeout , ok := ContextRequestTimeout ( cp . ctx ) ; ok {
timer = time . AfterFunc ( timeout , func ( ) {
cancel ( )
responded . Do ( func ( ) {
responded . Do ( func ( ) {
h . conn . writeJSON ( cp . ctx , answer , false )
resp := msg . errorResponse ( & internalServerError { errcodeTimeout , errMsgTimeout } )
h . conn . writeJSON ( cp . ctx , resp , true )
} )
} )
}
} )
for _ , n := range cp . notifiers {
}
n . activate ( )
}
answer := h . handleCallMsg ( cp , msg )
} )
if timer != nil {
timer . Stop ( )
}
h . addSubscriptions ( cp . notifiers )
if answer != nil {
responded . Do ( func ( ) {
h . conn . writeJSON ( cp . ctx , answer , false )
} )
}
for _ , n := range cp . notifiers {
n . activate ( )
}
}
}
// close cancels all requests except for inflightReq and waits for
// close cancels all requests except for inflightReq and waits for
@ -349,23 +388,60 @@ func (h *handler) startCallProc(fn func(*callProc)) {
} ( )
} ( )
}
}
// handleImmediate executes non-call messages. It returns false if the message is a
// handleResponse processes method call responses.
// call or requires a reply.
func ( h * handler ) handleResponses ( batch [ ] * jsonrpcMessage , handleCall func ( * jsonrpcMessage ) ) {
func ( h * handler ) handleImmediate ( msg * jsonrpcMessage ) bool {
var resolvedops [ ] * requestOp
start := time . Now ( )
handleResp := func ( msg * jsonrpcMessage ) {
switch {
op := h . respWait [ string ( msg . ID ) ]
case msg . isNotification ( ) :
if op == nil {
if strings . HasSuffix ( msg . Method , notificationMethodSuffix ) {
h . log . Debug ( "Unsolicited RPC response" , "reqid" , idForLog { msg . ID } )
h . handleSubscriptionResult ( msg )
return
return true
}
resolvedops = append ( resolvedops , op )
delete ( h . respWait , string ( msg . ID ) )
// For subscription responses, start the subscription if the server
// indicates success. EthSubscribe gets unblocked in either case through
// the op.resp channel.
if op . sub != nil {
if msg . Error != nil {
op . err = msg . Error
} else {
op . err = json . Unmarshal ( msg . Result , & op . sub . subid )
if op . err == nil {
go op . sub . run ( )
h . clientSubs [ op . sub . subid ] = op . sub
}
}
}
if ! op . hadResponse {
op . hadResponse = true
op . resp <- batch
}
}
return false
}
case msg . isResponse ( ) :
h . handleResponse ( msg )
for _ , msg := range batch {
h . log . Trace ( "Handled RPC response" , "reqid" , idForLog { msg . ID } , "duration" , time . Since ( start ) )
start := time . Now ( )
return true
switch {
default :
case msg . isResponse ( ) :
return false
handleResp ( msg )
h . log . Trace ( "Handled RPC response" , "reqid" , idForLog { msg . ID } , "duration" , time . Since ( start ) )
case msg . isNotification ( ) :
if strings . HasSuffix ( msg . Method , notificationMethodSuffix ) {
h . handleSubscriptionResult ( msg )
continue
}
handleCall ( msg )
default :
handleCall ( msg )
}
}
for _ , op := range resolvedops {
h . removeRequestOp ( op )
}
}
}
}
@ -381,33 +457,6 @@ func (h *handler) handleSubscriptionResult(msg *jsonrpcMessage) {
}
}
}
}
// handleResponse processes method call responses.
func ( h * handler ) handleResponse ( msg * jsonrpcMessage ) {
op := h . respWait [ string ( msg . ID ) ]
if op == nil {
h . log . Debug ( "Unsolicited RPC response" , "reqid" , idForLog { msg . ID } )
return
}
delete ( h . respWait , string ( msg . ID ) )
// For normal responses, just forward the reply to Call/BatchCall.
if op . sub == nil {
op . resp <- msg
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 . run ( )
h . clientSubs [ op . sub . subid ] = op . sub
}
}
// handleCallMsg executes a call message and returns the answer.
// handleCallMsg executes a call message and returns the answer.
func ( h * handler ) handleCallMsg ( ctx * callProc , msg * jsonrpcMessage ) * jsonrpcMessage {
func ( h * handler ) handleCallMsg ( ctx * callProc , msg * jsonrpcMessage ) * jsonrpcMessage {
start := time . Now ( )
start := time . Now ( )
@ -416,6 +465,7 @@ func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMess
h . handleCall ( ctx , msg )
h . handleCall ( ctx , msg )
h . log . Debug ( "Served " + msg . Method , "duration" , time . Since ( start ) )
h . log . Debug ( "Served " + msg . Method , "duration" , time . Since ( start ) )
return nil
return nil
case msg . isCall ( ) :
case msg . isCall ( ) :
resp := h . handleCall ( ctx , msg )
resp := h . handleCall ( ctx , msg )
var ctx [ ] interface { }
var ctx [ ] interface { }
@ -430,8 +480,10 @@ func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMess
h . log . Debug ( "Served " + msg . Method , ctx ... )
h . log . Debug ( "Served " + msg . Method , ctx ... )
}
}
return resp
return resp
case msg . hasValidID ( ) :
case msg . hasValidID ( ) :
return msg . errorResponse ( & invalidRequestError { "invalid request" } )
return msg . errorResponse ( & invalidRequestError { "invalid request" } )
default :
default :
return errorMessage ( & invalidRequestError { "invalid request" } )
return errorMessage ( & invalidRequestError { "invalid request" } )
}
}
@ -451,12 +503,14 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage
if callb == nil {
if callb == nil {
return msg . errorResponse ( & methodNotFoundError { method : msg . Method } )
return msg . errorResponse ( & methodNotFoundError { method : msg . Method } )
}
}
args , err := parsePositionalArguments ( msg . Params , callb . argTypes )
args , err := parsePositionalArguments ( msg . Params , callb . argTypes )
if err != nil {
if err != nil {
return msg . errorResponse ( & invalidParamsError { err . Error ( ) } )
return msg . errorResponse ( & invalidParamsError { err . Error ( ) } )
}
}
start := time . Now ( )
start := time . Now ( )
answer := h . runMethod ( cp . ctx , msg , callb , args )
answer := h . runMethod ( cp . ctx , msg , callb , args )
// Collect the statistics for RPC calls if metrics is enabled.
// Collect the statistics for RPC calls if metrics is enabled.
// We only care about pure rpc call. Filter out subscription.
// We only care about pure rpc call. Filter out subscription.
if callb != h . unsubscribeCb {
if callb != h . unsubscribeCb {
@ -469,6 +523,7 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage
rpcServingTimer . UpdateSince ( start )
rpcServingTimer . UpdateSince ( start )
updateServeTimeHistogram ( msg . Method , answer . Error == nil , time . Since ( start ) )
updateServeTimeHistogram ( msg . Method , answer . Error == nil , time . Since ( start ) )
}
}
return answer
return answer
}
}