tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/duplex/rpc/rpc.go (about) 1 package rpc 2 3 import ( 4 "context" 5 "errors" 6 7 "github.com/mitchellh/mapstructure" 8 "tractor.dev/toolkit-go/duplex/codec" 9 "tractor.dev/toolkit-go/duplex/mux" 10 ) 11 12 // A Caller is able to perform remote calls. 13 // 14 // Call makes synchronous calls to the remote selector passing args and putting the response 15 // value(s) in reply. Both args and reply can be nil. Args can be a channel of any 16 // values for asynchronously streaming multiple values from another goroutine, however 17 // the call will still block until a response is sent. If there is an error making the call 18 // an error is returned, and if an error is returned by the remote handler a RemoteError 19 // is returned. Multiple reply parameters can be provided in order to receive multi-valued 20 // returns from the remote call. 21 // 22 // A Response is also returned for advanced operations. For example, you can check 23 // if the call is continued, meaning the underlying channel will be kept open for either 24 // streaming back more results or using the channel as a full duplex byte stream. 25 type Caller interface { 26 Call(ctx context.Context, selector string, params any, reply ...any) (*Response, error) 27 } 28 29 // CallHeader is the first value encoded over the channel to make a call. 30 type CallHeader struct { 31 S string // Selector 32 } 33 34 // Call is used on the responding side of a call and is passed to the handler. 35 // Call has a Caller so it can be used to make calls back to the calling side. 36 type Call struct { 37 CallHeader 38 39 Caller Caller 40 Decoder codec.Decoder 41 Context context.Context 42 43 mux.Channel 44 } 45 46 func (c *Call) Selector() string { 47 return c.S 48 } 49 50 // Receive will decode an incoming value from the underlying channel. It can be 51 // called more than once when multiple values are expected, but should always be 52 // called once in a handler. It can be called with nil to discard the value. 53 func (c *Call) Receive(v interface{}) error { 54 if v == nil { 55 var discard []byte 56 v = &discard 57 } 58 return c.Decoder.Decode(v) 59 } 60 61 // ResponseHeader is the value encoded over the channel to indicate a response. 62 type ResponseHeader struct { 63 E *string // Error 64 C bool // Continue: after parsing response, keep stream open for whatever protocol 65 } 66 67 // Response is used on the calling side to represent a response and allow access 68 // to the ResponseHeader data, the reply value, the underlying channel, and methods 69 // to send or receive encoded values over the channel if Continue was set on the 70 // ResponseHeader. 71 type Response struct { 72 ResponseHeader 73 Value any 74 Channel mux.Channel 75 76 codec codec.Codec 77 } 78 79 func (r *Response) Err() error { 80 if r.E == nil { 81 return nil 82 } 83 return errors.New(*r.E) 84 } 85 86 func (r *Response) Continue() bool { 87 return r.C 88 } 89 90 // Send encodes a value over the underlying channel if it is still open. 91 func (r *Response) Send(v interface{}) error { 92 return r.codec.Encoder(r.Channel).Encode(v) 93 } 94 95 // Receive decodes a value from the underlying channel if it is still open. 96 func (r *Response) Receive(v interface{}) error { 97 return r.codec.Decoder(r.Channel).Decode(v) 98 } 99 100 func (r *Response) Close() error { 101 return r.Channel.Close() 102 } 103 104 func (r *Response) CloseWrite() error { 105 return r.Channel.CloseWrite() 106 } 107 108 // Responder is used by handlers to initiate a response and send values to the caller. 109 type Responder interface { 110 // Return sends a return value, which can be an error, and closes the channel. 111 Return(...any) error 112 113 // Continue sets the response to keep the channel open after sending a return value, 114 // and returns the underlying channel for you to take control of. If called, you 115 // become responsible for closing the channel. 116 Continue(...any) (mux.Channel, error) 117 118 // Send encodes a value over the underlying channel, but does not initiate a response, 119 // so it must be used after calling Continue. 120 Send(interface{}) error 121 } 122 123 type responder struct { 124 responded bool 125 header *ResponseHeader 126 ch mux.Channel 127 c codec.Codec 128 } 129 130 func (r *responder) Send(v interface{}) error { 131 return r.c.Encoder(r.ch).Encode(v) 132 } 133 134 func (r *responder) Return(v ...any) error { 135 return r.respond(v, false) 136 } 137 138 func (r *responder) Continue(v ...any) (mux.Channel, error) { 139 return r.ch, r.respond(v, true) 140 } 141 142 func (r *responder) respond(values []any, continue_ bool) error { 143 r.responded = true 144 r.header.C = continue_ 145 146 // if values is a single error, set values to [nil] 147 // and put error in header 148 if len(values) == 1 { 149 var e error 150 var ok bool 151 if e, ok = values[0].(error); ok { 152 values = []any{nil} 153 } 154 if e != nil { 155 var errStr = e.Error() 156 r.header.E = &errStr 157 } 158 } 159 160 if err := r.Send(r.header); err != nil { 161 return err 162 } 163 164 // The original calling convention expects at least one return, so return 165 // `nil` if there is no other return value. 166 if len(values) == 0 { 167 values = []any{nil} 168 } 169 for _, v := range values { 170 if err := r.Send(v); err != nil { 171 return err 172 } 173 } 174 175 if !continue_ { 176 return r.ch.Close() 177 } 178 179 return nil 180 } 181 182 // ReceiveNotify takes a continued response and sends received values to a channel, 183 // until an error is returned or the context finishes. In either case, the response 184 // and the channel will be closed. 185 func ReceiveNotify[T any](ctx context.Context, resp *Response, ch chan T) error { 186 defer close(ch) 187 defer resp.Close() 188 for { 189 var v any 190 if err := resp.Receive(&v); err != nil { 191 return err 192 } 193 var vv T 194 if err := mapstructure.Decode(v, &vv); err != nil { 195 return err 196 } 197 select { 198 case <-ctx.Done(): 199 return nil 200 default: 201 ch <- vv 202 } 203 } 204 }