github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/rpc/server.go (about) 1 package rpc 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "runtime" 8 "strings" 9 "sync" 10 "sync/atomic" 11 12 "github.com/quickchainproject/quickchain/log" 13 "gopkg.in/fatih/set.v0" 14 ) 15 16 const MetadataApi = "rpc" 17 18 // CodecOption specifies which type of messages this codec supports 19 type CodecOption int 20 21 const ( 22 // OptionMethodInvocation is an indication that the codec supports RPC method calls 23 OptionMethodInvocation CodecOption = 1 << iota 24 25 // OptionSubscriptions is an indication that the codec suports RPC notifications 26 OptionSubscriptions = 1 << iota // support pub sub 27 ) 28 29 // NewServer will create a new server instance with no registered handlers. 30 func NewServer() *Server { 31 server := &Server{ 32 services: make(serviceRegistry), 33 codecs: set.New(), 34 run: 1, 35 } 36 37 // register a default service which will provide meta information about the RPC service such as the services and 38 // methods it offers. 39 rpcService := &RPCService{server} 40 server.RegisterName(MetadataApi, rpcService) 41 42 return server 43 } 44 45 // RPCService gives meta information about the server. 46 // e.g. gives information about the loaded modules. 47 type RPCService struct { 48 server *Server 49 } 50 51 // Modules returns the list of RPC services with their version number 52 func (s *RPCService) Modules() map[string]string { 53 modules := make(map[string]string) 54 for name := range s.server.services { 55 modules[name] = "1.0" 56 } 57 return modules 58 } 59 60 // RegisterName will create a service for the given rcvr type under the given name. When no methods on the given rcvr 61 // match the criteria to be either a RPC method or a subscription an error is returned. Otherwise a new service is 62 // created and added to the service collection this server instance serves. 63 func (s *Server) RegisterName(name string, rcvr interface{}) error { 64 if s.services == nil { 65 s.services = make(serviceRegistry) 66 } 67 68 svc := new(service) 69 svc.typ = reflect.TypeOf(rcvr) 70 rcvrVal := reflect.ValueOf(rcvr) 71 72 if name == "" { 73 return fmt.Errorf("no service name for type %s", svc.typ.String()) 74 } 75 if !isExported(reflect.Indirect(rcvrVal).Type().Name()) { 76 return fmt.Errorf("%s is not exported", reflect.Indirect(rcvrVal).Type().Name()) 77 } 78 79 methods, subscriptions := suitableCallbacks(rcvrVal, svc.typ) 80 81 // already a previous service register under given sname, merge methods/subscriptions 82 if regsvc, present := s.services[name]; present { 83 if len(methods) == 0 && len(subscriptions) == 0 { 84 return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr) 85 } 86 for _, m := range methods { 87 regsvc.callbacks[formatName(m.method.Name)] = m 88 } 89 for _, s := range subscriptions { 90 regsvc.subscriptions[formatName(s.method.Name)] = s 91 } 92 return nil 93 } 94 95 svc.name = name 96 svc.callbacks, svc.subscriptions = methods, subscriptions 97 98 if len(svc.callbacks) == 0 && len(svc.subscriptions) == 0 { 99 return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr) 100 } 101 102 s.services[svc.name] = svc 103 return nil 104 } 105 106 // serveRequest will reads requests from the codec, calls the RPC callback and 107 // writes the response to the given codec. 108 // 109 // If singleShot is true it will process a single request, otherwise it will handle 110 // requests until the codec returns an error when reading a request (in most cases 111 // an EOF). It executes requests in parallel when singleShot is false. 112 func (s *Server) serveRequest(codec ServerCodec, singleShot bool, options CodecOption, ctx context.Context) error { 113 var pend sync.WaitGroup 114 115 defer func() { 116 if err := recover(); err != nil { 117 const size = 64 << 10 118 buf := make([]byte, size) 119 buf = buf[:runtime.Stack(buf, false)] 120 log.Error(string(buf)) 121 } 122 s.codecsMu.Lock() 123 s.codecs.Remove(codec) 124 s.codecsMu.Unlock() 125 }() 126 127 // ctx, cancel := context.WithCancel(context.Background()) 128 ctx, cancel := context.WithCancel(ctx) 129 defer cancel() 130 131 // if the codec supports notification include a notifier that callbacks can use 132 // to send notification to clients. It is thight to the codec/connection. If the 133 // connection is closed the notifier will stop and cancels all active subscriptions. 134 if options&OptionSubscriptions == OptionSubscriptions { 135 ctx = context.WithValue(ctx, notifierKey{}, newNotifier(codec)) 136 } 137 s.codecsMu.Lock() 138 if atomic.LoadInt32(&s.run) != 1 { // server stopped 139 s.codecsMu.Unlock() 140 return &shutdownError{} 141 } 142 s.codecs.Add(codec) 143 s.codecsMu.Unlock() 144 145 // test if the server is ordered to stop 146 for atomic.LoadInt32(&s.run) == 1 { 147 reqs, batch, err := s.readRequest(codec) 148 if err != nil { 149 // If a parsing error occurred, send an error 150 if err.Error() != "EOF" { 151 log.Debug(fmt.Sprintf("read error %v\n", err)) 152 codec.Write(codec.CreateErrorResponse(nil, err)) 153 } 154 // Error or end of stream, wait for requests and tear down 155 pend.Wait() 156 return nil 157 } 158 159 // check if server is ordered to shutdown and return an error 160 // telling the client that his request failed. 161 if atomic.LoadInt32(&s.run) != 1 { 162 err = &shutdownError{} 163 if batch { 164 resps := make([]interface{}, len(reqs)) 165 for i, r := range reqs { 166 resps[i] = codec.CreateErrorResponse(&r.id, err) 167 } 168 codec.Write(resps) 169 } else { 170 codec.Write(codec.CreateErrorResponse(&reqs[0].id, err)) 171 } 172 return nil 173 } 174 // If a single shot request is executing, run and return immediately 175 if singleShot { 176 if batch { 177 s.execBatch(ctx, codec, reqs) 178 } else { 179 s.exec(ctx, codec, reqs[0]) 180 } 181 return nil 182 } 183 // For multi-shot connections, start a goroutine to serve and loop back 184 pend.Add(1) 185 186 go func(reqs []*serverRequest, batch bool) { 187 defer pend.Done() 188 if batch { 189 s.execBatch(ctx, codec, reqs) 190 } else { 191 s.exec(ctx, codec, reqs[0]) 192 } 193 }(reqs, batch) 194 } 195 return nil 196 } 197 198 // ServeCodec reads incoming requests from codec, calls the appropriate callback and writes the 199 // response back using the given codec. It will block until the codec is closed or the server is 200 // stopped. In either case the codec is closed. 201 func (s *Server) ServeCodec(codec ServerCodec, options CodecOption) { 202 defer codec.Close() 203 s.serveRequest(codec, false, options, context.Background()) 204 } 205 206 // ServeSingleRequest reads and processes a single RPC request from the given codec. It will not 207 // close the codec unless a non-recoverable error has occurred. Note, this method will return after 208 // a single request has been processed! 209 func (s *Server) ServeSingleRequest(codec ServerCodec, options CodecOption, ctx context.Context) { 210 s.serveRequest(codec, true, options, ctx) 211 } 212 213 // Stop will stop reading new requests, wait for stopPendingRequestTimeout to allow pending requests to finish, 214 // close all codecs which will cancel pending requests/subscriptions. 215 func (s *Server) Stop() { 216 if atomic.CompareAndSwapInt32(&s.run, 1, 0) { 217 log.Debug("RPC Server shutdown initiatied") 218 s.codecsMu.Lock() 219 defer s.codecsMu.Unlock() 220 s.codecs.Each(func(c interface{}) bool { 221 c.(ServerCodec).Close() 222 return true 223 }) 224 } 225 } 226 227 // createSubscription will call the subscription callback and returns the subscription id or error. 228 func (s *Server) createSubscription(ctx context.Context, c ServerCodec, req *serverRequest) (ID, error) { 229 // subscription have as first argument the context following optional arguments 230 args := []reflect.Value{req.callb.rcvr, reflect.ValueOf(ctx)} 231 args = append(args, req.args...) 232 reply := req.callb.method.Func.Call(args) 233 234 if !reply[1].IsNil() { // subscription creation failed 235 return "", reply[1].Interface().(error) 236 } 237 238 return reply[0].Interface().(*Subscription).ID, nil 239 } 240 241 // handle executes a request and returns the response from the callback. 242 func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverRequest) (interface{}, func()) { 243 if req.err != nil { 244 return codec.CreateErrorResponse(&req.id, req.err), nil 245 } 246 247 if req.isUnsubscribe { // cancel subscription, first param must be the subscription id 248 if len(req.args) >= 1 && req.args[0].Kind() == reflect.String { 249 notifier, supported := NotifierFromContext(ctx) 250 if !supported { // interface doesn't support subscriptions (e.g. http) 251 return codec.CreateErrorResponse(&req.id, &callbackError{ErrNotificationsUnsupported.Error()}), nil 252 } 253 254 subid := ID(req.args[0].String()) 255 if err := notifier.unsubscribe(subid); err != nil { 256 return codec.CreateErrorResponse(&req.id, &callbackError{err.Error()}), nil 257 } 258 259 return codec.CreateResponse(req.id, true), nil 260 } 261 return codec.CreateErrorResponse(&req.id, &invalidParamsError{"Expected subscription id as first argument"}), nil 262 } 263 264 if req.callb.isSubscribe { 265 subid, err := s.createSubscription(ctx, codec, req) 266 if err != nil { 267 return codec.CreateErrorResponse(&req.id, &callbackError{err.Error()}), nil 268 } 269 270 // active the subscription after the sub id was successfully sent to the client 271 activateSub := func() { 272 notifier, _ := NotifierFromContext(ctx) 273 notifier.activate(subid, req.svcname) 274 } 275 276 return codec.CreateResponse(req.id, subid), activateSub 277 } 278 279 // regular RPC call, prepare arguments 280 if len(req.args) != len(req.callb.argTypes) { 281 rpcErr := &invalidParamsError{fmt.Sprintf("%s%s%s expects %d parameters, got %d", 282 req.svcname, serviceMethodSeparator, req.callb.method.Name, 283 len(req.callb.argTypes), len(req.args))} 284 return codec.CreateErrorResponse(&req.id, rpcErr), nil 285 } 286 287 arguments := []reflect.Value{req.callb.rcvr} 288 if req.callb.hasCtx { 289 arguments = append(arguments, reflect.ValueOf(ctx)) 290 } 291 if len(req.args) > 0 { 292 arguments = append(arguments, req.args...) 293 } 294 295 // execute RPC method and return result 296 reply := req.callb.method.Func.Call(arguments) 297 if len(reply) == 0 { 298 return codec.CreateResponse(req.id, nil), nil 299 } 300 301 if req.callb.errPos >= 0 { // test if method returned an error 302 if !reply[req.callb.errPos].IsNil() { 303 e := reply[req.callb.errPos].Interface().(error) 304 res := codec.CreateErrorResponse(&req.id, &callbackError{e.Error()}) 305 return res, nil 306 } 307 } 308 return codec.CreateResponse(req.id, reply[0].Interface()), nil 309 } 310 311 // exec executes the given request and writes the result back using the codec. 312 func (s *Server) exec(ctx context.Context, codec ServerCodec, req *serverRequest) { 313 var response interface{} 314 var callback func() 315 if req.err != nil { 316 response = codec.CreateErrorResponse(&req.id, req.err) 317 } else { 318 response, callback = s.handle(ctx, codec, req) 319 } 320 321 if err := codec.Write(response); err != nil { 322 log.Error(fmt.Sprintf("%v\n", err)) 323 codec.Close() 324 } 325 326 // when request was a subscribe request this allows these subscriptions to be actived 327 if callback != nil { 328 callback() 329 } 330 } 331 332 // execBatch executes the given requests and writes the result back using the codec. 333 // It will only write the response back when the last request is processed. 334 func (s *Server) execBatch(ctx context.Context, codec ServerCodec, requests []*serverRequest) { 335 responses := make([]interface{}, len(requests)) 336 var callbacks []func() 337 for i, req := range requests { 338 if req.err != nil { 339 responses[i] = codec.CreateErrorResponse(&req.id, req.err) 340 } else { 341 var callback func() 342 if responses[i], callback = s.handle(ctx, codec, req); callback != nil { 343 callbacks = append(callbacks, callback) 344 } 345 } 346 } 347 348 if err := codec.Write(responses); err != nil { 349 log.Error(fmt.Sprintf("%v\n", err)) 350 codec.Close() 351 } 352 353 // when request holds one of more subscribe requests this allows these subscriptions to be activated 354 for _, c := range callbacks { 355 c() 356 } 357 } 358 359 // readRequest requests the next (batch) request from the codec. It will return the collection 360 // of requests, an indication if the request was a batch, the invalid request identifier and an 361 // error when the request could not be read/parsed. 362 func (s *Server) readRequest(codec ServerCodec) ([]*serverRequest, bool, Error) { 363 reqs, batch, err := codec.ReadRequestHeaders() 364 if err != nil { 365 return nil, batch, err 366 } 367 368 requests := make([]*serverRequest, len(reqs)) 369 370 // verify requests 371 for i, r := range reqs { 372 var ok bool 373 var svc *service 374 375 if r.err != nil { 376 requests[i] = &serverRequest{id: r.id, err: r.err} 377 continue 378 } 379 380 if r.isPubSub && strings.HasSuffix(r.method, unsubscribeMethodSuffix) { 381 requests[i] = &serverRequest{id: r.id, isUnsubscribe: true} 382 argTypes := []reflect.Type{reflect.TypeOf("")} // expect subscription id as first arg 383 if args, err := codec.ParseRequestArguments(argTypes, r.params); err == nil { 384 requests[i].args = args 385 } else { 386 requests[i].err = &invalidParamsError{err.Error()} 387 } 388 continue 389 } 390 391 if svc, ok = s.services[r.service]; !ok { // rpc method isn't available 392 requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.service, r.method}} 393 continue 394 } 395 396 if r.isPubSub { // qct_subscribe, r.method contains the subscription method name 397 if callb, ok := svc.subscriptions[r.method]; ok { 398 requests[i] = &serverRequest{id: r.id, svcname: svc.name, callb: callb} 399 if r.params != nil && len(callb.argTypes) > 0 { 400 argTypes := []reflect.Type{reflect.TypeOf("")} 401 argTypes = append(argTypes, callb.argTypes...) 402 if args, err := codec.ParseRequestArguments(argTypes, r.params); err == nil { 403 requests[i].args = args[1:] // first one is service.method name which isn't an actual argument 404 } else { 405 requests[i].err = &invalidParamsError{err.Error()} 406 } 407 } 408 } else { 409 requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.service, r.method}} 410 } 411 continue 412 } 413 414 if callb, ok := svc.callbacks[r.method]; ok { // lookup RPC method 415 requests[i] = &serverRequest{id: r.id, svcname: svc.name, callb: callb} 416 if r.params != nil && len(callb.argTypes) > 0 { 417 if args, err := codec.ParseRequestArguments(callb.argTypes, r.params); err == nil { 418 requests[i].args = args 419 } else { 420 requests[i].err = &invalidParamsError{err.Error()} 421 } 422 } 423 continue 424 } 425 426 requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.service, r.method}} 427 } 428 429 return requests, batch, nil 430 }