func (client *Client) send(ctx context.Context, call *Call) error { if client.shutdown || client.closing { return ErrShutdown } // Every call gets its own req socket sock, err := client.ctx.NewSocket(zmq.REQ) if err != nil { return err } defer sock.Close() // Connect it to the router if err := sock.Connect(RouterURL); err != nil { return err } // Marshal the outgoing message msgBytes, err := proto.Marshal(call.Req) if err != nil { return err } // Envelope the message reqID := uuid.NewV4() envelope := &Request{ UUID: reqID.Bytes(), Path: proto.String(fmt.Sprintf("zrpc://%s/%s", call.Service, util.GetMessageName(call.Req))), Payload: msgBytes, } // If request has a timeout, send it in the request envelope d, ok := ctx.Deadline() if ok { envelope.Expires = proto.Int64(d.Unix()) } // Marshal the outgoing envelope envBytes, err := proto.Marshal(envelope) if err != nil { return err } // Send it if _, err := sock.SendBytes(envBytes, 0); err != nil { return err } handleResponse := func(state zmq.State) error { respBytes, err := sock.RecvBytes(0) if err != nil { return err } // Unmarshal the response envelope resp := &Response{} if err := proto.Unmarshal(respBytes, resp); err != nil { return err } // Make sure the same message ID was received respID, err := uuid.FromBytes(resp.UUID) if err != nil || !uuid.Equal(reqID, respID) { glog.Errorf("Mismatching message IDs, sent '%s', got '%s'", reqID, respID) return ErrMessageMismatch } // Check if there is an error if resp.Error != nil { return NewClientError(resp.Error.GetMessage(), int(resp.GetStatusCode())) } // Decode the actual message (if exists and wanted) if call.Resp != nil && len(resp.Payload) > 0 { if err := proto.Unmarshal(resp.Payload, call.Resp); err != nil { return err } } // This is insane, but the reactor runs until an error is returned... return errDone } // Use a reactor to be able to utilize the calls cancelc reactor := zmq.NewReactor() reactor.AddSocket(sock, zmq.POLLIN, handleResponse) reactor.AddChannel(call.cancelc, 1, func(interface{}) error { return ErrCancel }) // Poll for a short interval to be able to return to the channel handling if err := reactor.Run(time.Millisecond * 50); err != errDone { return err } return nil }
// suitableMethods returns suitable Rpc methods of typ, it will report // error using log if reportErr is true. func suitableMethods(typ reflect.Type, reportErr bool) map[string]*methodType { methods := make(map[string]*methodType) for m := 0; m < typ.NumMethod(); m++ { method := typ.Method(m) mtype := method.Type mname := method.Name // Method must be exported. if method.PkgPath != "" { continue } // Method can have 2 or 3 ins: receiver, *args, (optional) *reply. if mtype.NumIn() < 2 { if reportErr { glog.Error("zrpc.Register: method ", mname, " has wrong number of ins: ", mtype.NumIn()) } continue } // First arg need not be a pointer. argType := mtype.In(1) if !isExportedOrBuiltinType(argType) { if reportErr { glog.Error("zrpc.Register: ", mname, " argument type not exported: ", argType) } continue } // Second arg is optional var replyType reflect.Type if mtype.NumIn() > 2 { // Second arg must be a pointer. replyType = mtype.In(2) if replyType.Kind() != reflect.Ptr { if reportErr { glog.Error("zrpc.Register: method ", mname, " reply type not a pointer: ", replyType) } continue } // Reply type must be exported. if !isExportedOrBuiltinType(replyType) { if reportErr { glog.Error("zrpc.Register: method ", mname, " reply type not exported: ", replyType) } continue } } // Method needs one out. if mtype.NumOut() != 1 { if reportErr { glog.Error("zrpc.Register: method ", mname, " has wrong number of outs: ", mtype.NumOut()) } continue } // The return type of the method must be error. if returnType := mtype.Out(0); returnType != typeOfError { if reportErr { glog.Error("zrpc.Register: method ", mname, " returns ", returnType.String(), " not error") } continue } // Use the request arg as key var argv reflect.Value if argType.Kind() == reflect.Ptr { argv = reflect.New(argType.Elem()) } else { argv = reflect.New(argType) } methods[util.GetMessageName(argv.Interface().(proto.Message))] = &methodType{method: method, ArgType: argType, ReplyType: replyType} } return methods }