director.go 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. package proxy
  2. import (
  3. "errors"
  4. "fmt"
  5. "log"
  6. "net/url"
  7. "sync"
  8. "time"
  9. )
  10. const (
  11. // amount of time an endpoint will be held in a failed
  12. // state before being reconsidered for proxied requests
  13. endpointFailureWait = 5 * time.Second
  14. )
  15. func newDirector(urls []string) (*director, error) {
  16. if len(urls) == 0 {
  17. return nil, errors.New("one or more endpoints required")
  18. }
  19. endpoints := make([]*endpoint, len(urls))
  20. for i, v := range urls {
  21. u, err := url.Parse(v)
  22. if err != nil {
  23. return nil, fmt.Errorf("invalid endpoint %q: %v", v, err)
  24. }
  25. if u.Scheme == "" {
  26. return nil, fmt.Errorf("invalid endpoint %q: scheme required", v)
  27. }
  28. if u.Host == "" {
  29. return nil, fmt.Errorf("invalid endpoint %q: host empty", v)
  30. }
  31. endpoints[i] = newEndpoint(*u)
  32. }
  33. d := director{ep: endpoints}
  34. return &d, nil
  35. }
  36. type director struct {
  37. ep []*endpoint
  38. }
  39. func (d *director) endpoints() []*endpoint {
  40. filtered := make([]*endpoint, 0)
  41. for _, ep := range d.ep {
  42. if ep.Available {
  43. filtered = append(filtered, ep)
  44. }
  45. }
  46. return filtered
  47. }
  48. func newEndpoint(u url.URL) *endpoint {
  49. ep := endpoint{
  50. URL: u,
  51. Available: true,
  52. failFunc: timedUnavailabilityFunc(endpointFailureWait),
  53. }
  54. return &ep
  55. }
  56. type endpoint struct {
  57. sync.Mutex
  58. URL url.URL
  59. Available bool
  60. failFunc func(ep *endpoint)
  61. }
  62. func (ep *endpoint) Failed() {
  63. ep.Lock()
  64. if !ep.Available {
  65. ep.Unlock()
  66. return
  67. }
  68. ep.Available = false
  69. ep.Unlock()
  70. log.Printf("proxy: marked endpoint %s unavailable", ep.URL.String())
  71. if ep.failFunc == nil {
  72. log.Printf("proxy: no failFunc defined, endpoint %s will be unavailable forever.", ep.URL.String())
  73. return
  74. }
  75. ep.failFunc(ep)
  76. }
  77. func timedUnavailabilityFunc(wait time.Duration) func(*endpoint) {
  78. return func(ep *endpoint) {
  79. time.AfterFunc(wait, func() {
  80. ep.Available = true
  81. log.Printf("proxy: marked endpoint %s available", ep.URL.String())
  82. })
  83. }
  84. }