health.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. // Copyright 2018 fatedier, fatedier@gmail.com
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package health
  15. import (
  16. "context"
  17. "errors"
  18. "fmt"
  19. "net"
  20. "net/http"
  21. "time"
  22. "github.com/fatedier/frp/utils/log"
  23. )
  24. var (
  25. ErrHealthCheckType = errors.New("error health check type")
  26. )
  27. type HealthCheckMonitor struct {
  28. checkType string
  29. interval time.Duration
  30. timeout time.Duration
  31. maxFailedTimes int
  32. // For tcp
  33. addr string
  34. // For http
  35. url string
  36. failedTimes uint64
  37. statusOK bool
  38. statusNormalFn func()
  39. statusFailedFn func()
  40. ctx context.Context
  41. cancel context.CancelFunc
  42. l log.Logger
  43. }
  44. func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFailedTimes int, addr string, url string,
  45. statusNormalFn func(), statusFailedFn func()) *HealthCheckMonitor {
  46. if intervalS <= 0 {
  47. intervalS = 10
  48. }
  49. if timeoutS <= 0 {
  50. timeoutS = 3
  51. }
  52. if maxFailedTimes <= 0 {
  53. maxFailedTimes = 1
  54. }
  55. ctx, cancel := context.WithCancel(context.Background())
  56. return &HealthCheckMonitor{
  57. checkType: checkType,
  58. interval: time.Duration(intervalS) * time.Second,
  59. timeout: time.Duration(timeoutS) * time.Second,
  60. maxFailedTimes: maxFailedTimes,
  61. addr: addr,
  62. url: url,
  63. statusOK: false,
  64. statusNormalFn: statusNormalFn,
  65. statusFailedFn: statusFailedFn,
  66. ctx: ctx,
  67. cancel: cancel,
  68. }
  69. }
  70. func (monitor *HealthCheckMonitor) SetLogger(l log.Logger) {
  71. monitor.l = l
  72. }
  73. func (monitor *HealthCheckMonitor) Start() {
  74. go monitor.checkWorker()
  75. }
  76. func (monitor *HealthCheckMonitor) Stop() {
  77. monitor.cancel()
  78. }
  79. func (monitor *HealthCheckMonitor) checkWorker() {
  80. for {
  81. ctx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
  82. err := monitor.doCheck(ctx)
  83. // check if this monitor has been closed
  84. select {
  85. case <-ctx.Done():
  86. cancel()
  87. return
  88. default:
  89. cancel()
  90. }
  91. if err == nil {
  92. if monitor.l != nil {
  93. monitor.l.Trace("do one health check success")
  94. }
  95. if !monitor.statusOK && monitor.statusNormalFn != nil {
  96. if monitor.l != nil {
  97. monitor.l.Info("health check status change to success")
  98. }
  99. monitor.statusOK = true
  100. monitor.statusNormalFn()
  101. }
  102. } else {
  103. if monitor.l != nil {
  104. monitor.l.Warn("do one health check failed: %v", err)
  105. }
  106. monitor.failedTimes++
  107. if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil {
  108. if monitor.l != nil {
  109. monitor.l.Warn("health check status change to failed")
  110. }
  111. monitor.statusOK = false
  112. monitor.statusFailedFn()
  113. }
  114. }
  115. time.Sleep(monitor.interval)
  116. }
  117. }
  118. func (monitor *HealthCheckMonitor) doCheck(ctx context.Context) error {
  119. switch monitor.checkType {
  120. case "tcp":
  121. return monitor.doTcpCheck(ctx)
  122. case "http":
  123. return monitor.doHttpCheck(ctx)
  124. default:
  125. return ErrHealthCheckType
  126. }
  127. }
  128. func (monitor *HealthCheckMonitor) doTcpCheck(ctx context.Context) error {
  129. // if tcp address is not specified, always return nil
  130. if monitor.addr == "" {
  131. return nil
  132. }
  133. var d net.Dialer
  134. conn, err := d.DialContext(ctx, "tcp", monitor.addr)
  135. if err != nil {
  136. return err
  137. }
  138. conn.Close()
  139. return nil
  140. }
  141. func (monitor *HealthCheckMonitor) doHttpCheck(ctx context.Context) error {
  142. req, err := http.NewRequest("GET", monitor.url, nil)
  143. if err != nil {
  144. return err
  145. }
  146. resp, err := http.DefaultClient.Do(req)
  147. if err != nil {
  148. return err
  149. }
  150. if resp.StatusCode/100 != 2 {
  151. return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
  152. }
  153. return nil
  154. }