tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/duplex/rpc/handler.go (about) 1 package rpc 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "sync" 8 ) 9 10 // A Handler responds to an RPC request. 11 // 12 // RespondRPC should use Call to receive at least one input argument value, then use 13 // Responder to return a value or continue. Since an input argument value is always 14 // sent to the handler, a call to Receive on the Call value shoud always be done otherwise 15 // the call will block. You can call Receive with nil to discard the input value. If 16 // Responder is not used, a default value of nil is returned. 17 type Handler interface { 18 RespondRPC(Responder, *Call) 19 } 20 21 // The HandlerFunc type is an adapter to allow the use of ordinary functions as RPC handlers. 22 // If f is a function with the appropriate signature, HandlerFunc(f) is a Handler that calls f. 23 type HandlerFunc func(Responder, *Call) 24 25 // RespondRPC calls f(resp, call). 26 func (f HandlerFunc) RespondRPC(resp Responder, call *Call) { 27 f(resp, call) 28 } 29 30 // NotFoundHandler returns a simple handler that returns an error "not found". 31 func NotFoundHandler() Handler { 32 return HandlerFunc(func(r Responder, c *Call) { 33 r.Return(fmt.Errorf("not found: %s", c.Selector())) 34 }) 35 } 36 37 // RespondMux is an RPC call multiplexer. It matches the selector of each incoming call against a list of 38 // registered selector patterns and calls the handler for the pattern that most closely matches the selector. 39 // 40 // RespondMux also takes care of normalizing the selector to a path form "/foo/bar", allowing you to use 41 // this or the more conventional RPC dot form "foo.bar". 42 // 43 // Patterns match exact incoming selectors, or can end with a "/" or "." to indicate handling any selectors 44 // beginning with this pattern. Longer patterns take precedence over shorter ones, so that if there are 45 // handlers registered for both "foo." and "foo.bar.", the latter handler will be called for selectors 46 // beginning "foo.bar." and the former will receive calls for any other selectors prefixed with "foo.". 47 // 48 // Since RespondMux is also a Handler, you can use them for submuxing. If a pattern matches a handler that 49 // is a RespondMux, it will trim the matching selector prefix before matching against the sub RespondMux. 50 type RespondMux struct { 51 m map[string]muxEntry 52 es []muxEntry // slice of entries sorted from longest to shortest. 53 mu sync.RWMutex 54 } 55 56 type muxEntry struct { 57 h Handler 58 pattern string 59 } 60 61 type matcher interface { 62 Match(selector string) (h Handler, pattern string) 63 } 64 65 // cleanSelector returns the canonical selector for s, normalizing . separators to /. 66 func cleanSelector(s string) string { 67 if s == "" { 68 return "/" 69 } 70 if s[0] != '/' { 71 s = "/" + s 72 } 73 s = strings.ReplaceAll(s, ".", "/") 74 return s 75 } 76 77 // NewRespondMux allocates and returns a new RespondMux. 78 func NewRespondMux() *RespondMux { return new(RespondMux) } 79 80 // RespondRPC dispatches the call to the handler whose pattern most closely matches the selector. 81 func (m *RespondMux) RespondRPC(r Responder, c *Call) { 82 h, _ := m.Handler(c) 83 h.RespondRPC(r, c) 84 } 85 86 // Handler returns the handler to use for the given call, consulting 87 // c.Selector(). It always returns a non-nil handler. 88 // 89 // If there is no registered handler that applies to the request, Handler 90 // returns the FallbackHandler or if not set, a "not found" handler 91 // with an empty pattern. 92 func (m *RespondMux) Handler(c *Call) (h Handler, pattern string) { 93 m.mu.RLock() 94 defer m.mu.RUnlock() 95 96 h, pattern = m.Match(c.Selector()) 97 if h == nil { 98 h, pattern = NotFoundHandler(), "" 99 } 100 return 101 } 102 103 // Remove removes and returns the handler for the selector. 104 func (m *RespondMux) Remove(selector string) (h Handler) { 105 m.mu.Lock() 106 defer m.mu.Unlock() 107 108 selector = cleanSelector(selector) 109 h = m.m[selector].h 110 delete(m.m, selector) 111 112 return 113 } 114 115 // Match finds a handler given a selector string. 116 // Most-specific (longest) pattern wins. If a pattern handler 117 // is a submux, it will call Match with the selector minus the 118 // pattern. 119 func (m *RespondMux) Match(selector string) (h Handler, pattern string) { 120 selector = cleanSelector(selector) 121 122 // Check for exact match first. 123 v, ok := m.m[selector] 124 if ok { 125 return v.h, v.pattern 126 } 127 128 // Check for longest valid match. m.es contains all patterns 129 // that end in / sorted from longest to shortest. 130 for _, e := range m.es { 131 if strings.HasPrefix(selector, e.pattern) { 132 if m, ok := e.h.(matcher); ok { 133 return m.Match(strings.TrimPrefix(selector, e.pattern)) 134 } 135 return e.h, e.pattern 136 } 137 } 138 139 return nil, "" 140 } 141 142 // Handle registers the handler for the given pattern. 143 // If a handler already exists for pattern, Handle panics. 144 func (m *RespondMux) Handle(pattern string, handler Handler) { 145 m.mu.Lock() 146 defer m.mu.Unlock() 147 148 pattern = cleanSelector(pattern) 149 if _, ok := handler.(matcher); ok && pattern[len(pattern)-1] != '/' { 150 pattern = pattern + "/" 151 } 152 153 if handler == nil { 154 panic("rpc: nil handler") 155 } 156 if _, exist := m.m[pattern]; exist { 157 panic("rpc: multiple registrations for " + pattern) 158 } 159 160 if m.m == nil { 161 m.m = make(map[string]muxEntry) 162 } 163 e := muxEntry{h: handler, pattern: pattern} 164 m.m[pattern] = e 165 if pattern[len(pattern)-1] == '/' { 166 m.es = appendSorted(m.es, e) 167 } 168 } 169 170 func appendSorted(es []muxEntry, e muxEntry) []muxEntry { 171 n := len(es) 172 i := sort.Search(n, func(i int) bool { 173 return len(es[i].pattern) < len(e.pattern) 174 }) 175 if i == n { 176 return append(es, e) 177 } 178 // we now know that i points at where we want to insert 179 es = append(es, muxEntry{}) // try to grow the slice in place, any entry works. 180 copy(es[i+1:], es[i:]) // Move shorter entries down 181 es[i] = e 182 return es 183 }