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  }