tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/duplex/fn/handler.go (about) 1 package fn 2 3 import ( 4 "fmt" 5 "reflect" 6 "runtime" 7 "strings" 8 9 "tractor.dev/toolkit-go/duplex/mux" 10 "tractor.dev/toolkit-go/duplex/rpc" 11 ) 12 13 // HandlerFrom uses reflection to return a handler from either a function or 14 // methods from a struct. When a struct is used, HandlerFrom creates a RespondMux 15 // registering each method as a handler using its method name. From there, methods 16 // are treated just like functions. 17 // 18 // The registered methods can be limited by providing an interface type parameter: 19 // 20 // h := HandlerFrom[interface{ 21 // OnlyTheseMethods() 22 // WillBeRegistered() 23 // }](myHandlerImplementation) 24 // 25 // If a struct method matches the HandlerFunc signature, the method will be called 26 // directly with the handler arguments. Otherwise it will be wrapped as described below. 27 // 28 // Function handlers expect an array to use as arguments. If the incoming argument 29 // array is too large or too small, the handler returns an error. Functions can opt-in 30 // to take a final Call pointer argument, allowing the handler to give it the Call value 31 // being processed. Functions can return nothing which the handler returns as nil, or 32 // a single value which can be an error, or two values where one value is an error. 33 // In the latter case, the value is returned if the error is nil, otherwise just the 34 // error is returned. Handlers based on functions that return more than two values will 35 // simply ignore the remaining values. 36 // 37 // Structs that implement the Handler interface will be added as a catch-all handler 38 // along with their individual methods. This lets you implement dynamic methods. 39 func HandlerFrom[T any](v T) rpc.Handler { 40 rv := reflect.Indirect(reflect.ValueOf(v)) 41 switch rv.Type().Kind() { 42 case reflect.Func: 43 return fromFunc(reflect.ValueOf(v)) 44 case reflect.Struct: 45 // assume T is an interface 46 t := reflect.TypeOf((*T)(nil)).Elem() 47 if t.NumMethod() == 0 { 48 // could be inferred empty interface / any 49 // so then just use TypeOf v 50 t = reflect.TypeOf(v) 51 } 52 return fromMethods(v, t) 53 default: 54 panic("must be func or struct") 55 } 56 } 57 58 // Args is the expected argument value for calls made to HandlerFrom handlers. 59 // Since it is just a slice of empty interface values, you can alternatively use 60 // more specific slice types ([]int{}, etc) if all arguments are of the same type. 61 type Args []any 62 63 var handlerFuncType = reflect.TypeOf((*rpc.HandlerFunc)(nil)).Elem() 64 65 func fromMethods(rcvr interface{}, t reflect.Type) rpc.Handler { 66 // If `t` is an interface, `Convert()` wraps the value with that interface 67 // type. This makes sure that the Method(i) indexes match for getting both the 68 // name and implementation. 69 rcvrval := reflect.ValueOf(rcvr).Convert(t) 70 mux := rpc.NewRespondMux() 71 for i := 0; i < t.NumMethod(); i++ { 72 m := rcvrval.Method(i) 73 var h rpc.Handler 74 if m.CanConvert(handlerFuncType) { 75 h = m.Convert(handlerFuncType).Interface().(rpc.HandlerFunc) 76 } else { 77 h = fromFunc(m) 78 } 79 mux.Handle(t.Method(i).Name, h) 80 } 81 h, ok := rcvr.(rpc.Handler) 82 if ok { 83 mux.Handle("/", h) 84 } 85 return mux 86 } 87 88 var callRef = reflect.TypeOf((*rpc.Call)(nil)) 89 90 func fromFunc(fn reflect.Value) rpc.Handler { 91 fntyp := fn.Type() 92 // if the last argument in fn is an rpc.Call, add our call to fnParams 93 expectsCallParam := fntyp.NumIn() > 0 && fntyp.In(fntyp.NumIn()-1) == callRef 94 95 // if the last arg or first return in fn is a channel, we'll make a channel to stream back 96 expectsChanParam := fntyp.NumIn() > 0 && fntyp.In(fntyp.NumIn()-1).Kind() == reflect.Chan 97 var chanType reflect.Type 98 var chanStream bool 99 if expectsChanParam { 100 chanType = fntyp.In(fntyp.NumIn() - 1) 101 chanStream = true 102 } 103 if fntyp.NumOut() > 0 && fntyp.Out(0).Kind() == reflect.Chan { 104 chanType = fntyp.Out(0) 105 chanStream = true 106 } 107 108 return rpc.HandlerFunc(func(r rpc.Responder, c *rpc.Call) { 109 var params []any 110 111 defer func() { 112 if p := recover(); p != nil { 113 r.Return(fmt.Errorf("panic: %s [%s] %s(%s)", p, identifyPanic(), c.Selector(), params)) 114 } 115 }() 116 117 var ch any 118 if err := c.Receive(¶ms); err != nil { 119 r.Return(fmt.Errorf("fn: args: %s", err.Error())) 120 return 121 } 122 if expectsCallParam { 123 params = append(params, c) 124 } else if expectsChanParam { 125 // TODO: somehow pass buffer via CallHeader? 126 _ = chanType 127 //ch = reflect.MakeChan(chanType, 512).Interface() // not supported by tinygo 0.28.1 128 //params = append(params, ch) 129 } 130 ret, err := Call(fn.Interface(), params) 131 if err != nil { 132 r.Return(err) 133 return 134 } 135 if chanStream && fntyp.NumOut() > 0 && fntyp.Out(0).Kind() == reflect.Chan { 136 ch = ret[0] 137 ret = ret[1:] 138 } 139 if chanStream { 140 c, _ := r.Continue(ret...) 141 go streamValues(r, c, reflect.ValueOf(ch)) 142 return 143 } 144 r.Return(ret...) 145 }) 146 } 147 148 // streamValues will receive on the reflected Go channel and send over the 149 // duplex channel until an error is returned or either are closed. 150 func streamValues(r rpc.Responder, ch mux.Channel, valueCh reflect.Value) { 151 defer ch.Close() 152 for { 153 v, ok := valueCh.Recv() 154 if !ok { 155 return 156 } 157 if err := r.Send(v.Interface()); err != nil { 158 return 159 } 160 } 161 } 162 163 func identifyPanic() string { 164 var name, file string 165 var line int 166 var pc [16]uintptr 167 168 n := runtime.Callers(3, pc[:]) 169 for _, pc := range pc[:n] { 170 fn := runtime.FuncForPC(pc) 171 if fn == nil { 172 continue 173 } 174 file, line = fn.FileLine(pc) 175 name = fn.Name() 176 if !strings.HasPrefix(name, "runtime.") && !strings.HasPrefix(name, "reflect.") { 177 break 178 } 179 } 180 181 switch { 182 case name != "": 183 return fmt.Sprintf("%v:%v", name, line) 184 case file != "": 185 return fmt.Sprintf("%v:%v", file, line) 186 } 187 188 return fmt.Sprintf("pc:%x", pc) 189 }