github.com/xmidt-org/webpa-common@v1.11.9/xhttp/fanout/serviceEndpoints.go (about) 1 package fanout 2 3 import ( 4 "net/http" 5 "net/url" 6 "sync" 7 8 "github.com/xmidt-org/webpa-common/device" 9 "github.com/xmidt-org/webpa-common/service" 10 "github.com/xmidt-org/webpa-common/service/monitor" 11 "github.com/xmidt-org/webpa-common/service/servicehttp" 12 "github.com/xmidt-org/webpa-common/xhttp" 13 ) 14 15 // ServiceEndpoints is an Endpoints implementation that is driven by service discovery. 16 // This type is a monitor.Listener, and fanout URLs are computed from all configured 17 // instancers. 18 type ServiceEndpoints struct { 19 lock sync.RWMutex 20 keyFunc servicehttp.KeyFunc 21 accessorFactory service.AccessorFactory 22 accessors map[string]service.Accessor 23 } 24 25 // FanoutURLs uses the currently available discovered endpoints to produce a set of URLs. 26 // The original request is used to produce a hash key, then each accessor is consulted for 27 // the endpoint that matches that key. 28 func (se *ServiceEndpoints) FanoutURLs(original *http.Request) ([]*url.URL, error) { 29 hashKey, err := se.keyFunc(original) 30 if err != nil { 31 return nil, err 32 } 33 34 se.lock.RLock() 35 endpoints := make([]string, 0, len(se.accessors)) 36 for _, a := range se.accessors { 37 e, err := a.Get(hashKey) 38 if err != nil { 39 continue 40 } 41 42 endpoints = append(endpoints, e) 43 } 44 45 se.lock.RUnlock() 46 if len(endpoints) == 0 { 47 return []*url.URL{}, errNoFanoutURLs 48 } 49 return xhttp.ApplyURLParser(url.Parse, endpoints...) 50 } 51 52 // MonitorEvent supplies the monitor.Listener behavior. An accessor is created and stored under 53 // the event Key. 54 func (se *ServiceEndpoints) MonitorEvent(e monitor.Event) { 55 accessor := se.accessorFactory(e.Instances) 56 se.lock.Lock() 57 se.accessors[e.Key] = accessor 58 se.lock.Unlock() 59 } 60 61 // ServiceEndpointsOption is a strategy for configuring a ServiceEndpoints 62 type ServiceEndpointsOption func(*ServiceEndpoints) 63 64 // WithKeyFunc configures a hash function for the given service endpoints. If nil, 65 // then device.IDHashParser is used. 66 func WithKeyFunc(kf servicehttp.KeyFunc) ServiceEndpointsOption { 67 return func(se *ServiceEndpoints) { 68 if kf != nil { 69 se.keyFunc = kf 70 } else { 71 se.keyFunc = device.IDHashParser 72 } 73 } 74 } 75 76 // WithAccessorFactory configures an accessor factory for the given service endpoints. 77 // If nil, then service.DefaultAccessorFactory is used. 78 func WithAccessorFactory(af service.AccessorFactory) ServiceEndpointsOption { 79 return func(se *ServiceEndpoints) { 80 if af != nil { 81 se.accessorFactory = af 82 } else { 83 se.accessorFactory = service.DefaultAccessorFactory 84 } 85 } 86 } 87 88 // NewServiceEndpoints creates a ServiceEndpoints instance. By default, device.IDHashParser is used as the KeyFunc 89 // and service.DefaultAccessorFactory is used as the accessor factory. 90 func NewServiceEndpoints(options ...ServiceEndpointsOption) *ServiceEndpoints { 91 se := &ServiceEndpoints{ 92 keyFunc: device.IDHashParser, 93 accessorFactory: service.DefaultAccessorFactory, 94 accessors: make(map[string]service.Accessor), 95 } 96 97 for _, o := range options { 98 o(se) 99 } 100 101 return se 102 } 103 104 // ServiceEndpointsAlternate creates an alternate closure appropriate for NewEndpoints. This function 105 // allows service discovery to be used for fanout endpoints when no fixed endpoints are provided via Options. 106 // 107 // The returned Endpoints, being a ServiceEndpoints, will implement monitor.Listener. This function does not 108 // start listening to service discovery events. That must be done by application code. 109 func ServiceEndpointsAlternate(options ...ServiceEndpointsOption) func() (Endpoints, error) { 110 return func() (Endpoints, error) { 111 return NewServiceEndpoints(options...), nil 112 } 113 } 114 115 // MonitorEndpoints applies service discovery updates to the given Endpoints, if and only if the 116 // Endpoints implements monitor.Listener. If Endpoints does not implement monitor.Listener, this 117 // function return a nil monitor and a nil error. 118 func MonitorEndpoints(e Endpoints, options ...monitor.Option) (monitor.Interface, error) { 119 if l, ok := e.(monitor.Listener); ok { 120 options = append(options, monitor.WithListeners(l)) 121 return monitor.New(options...) 122 } 123 124 return nil, nil 125 }