1
0

uptime.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. package uptime
  2. import (
  3. "encoding/json"
  4. "log"
  5. "net/http"
  6. "strings"
  7. "time"
  8. "imuslab.com/zoraxy/mod/utils"
  9. )
  10. type Record struct {
  11. Timestamp int64
  12. ID string
  13. Name string
  14. URL string
  15. Protocol string
  16. Online bool
  17. StatusCode int
  18. Latency int64
  19. }
  20. type Target struct {
  21. ID string
  22. Name string
  23. URL string
  24. Protocol string
  25. }
  26. type Config struct {
  27. Targets []*Target
  28. Interval int
  29. MaxRecordsStore int
  30. }
  31. type Monitor struct {
  32. Config *Config
  33. OnlineStatusLog map[string][]*Record
  34. }
  35. // Default configs
  36. var exampleTarget = Target{
  37. ID: "example",
  38. Name: "Example",
  39. URL: "example.com",
  40. Protocol: "https",
  41. }
  42. // Create a new uptime monitor
  43. func NewUptimeMonitor(config *Config) (*Monitor, error) {
  44. //Create new monitor object
  45. thisMonitor := Monitor{
  46. Config: config,
  47. OnlineStatusLog: map[string][]*Record{},
  48. }
  49. //Start the endpoint listener
  50. ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
  51. done := make(chan bool)
  52. //Start the uptime check once first before entering loop
  53. thisMonitor.ExecuteUptimeCheck()
  54. go func() {
  55. for {
  56. select {
  57. case <-done:
  58. return
  59. case t := <-ticker.C:
  60. log.Println("Uptime updated - ", t.Unix())
  61. thisMonitor.ExecuteUptimeCheck()
  62. }
  63. }
  64. }()
  65. return &thisMonitor, nil
  66. }
  67. func (m *Monitor) ExecuteUptimeCheck() {
  68. for _, target := range m.Config.Targets {
  69. //For each target to check online, do the following
  70. var thisRecord Record
  71. if target.Protocol == "http" || target.Protocol == "https" {
  72. online, laterncy, statusCode := getWebsiteStatusWithLatency(target.URL)
  73. thisRecord = Record{
  74. Timestamp: time.Now().Unix(),
  75. ID: target.ID,
  76. Name: target.Name,
  77. URL: target.URL,
  78. Protocol: target.Protocol,
  79. Online: online,
  80. StatusCode: statusCode,
  81. Latency: laterncy,
  82. }
  83. //fmt.Println(thisRecord)
  84. } else {
  85. log.Println("Unknown protocol: " + target.Protocol + ". Skipping")
  86. continue
  87. }
  88. thisRecords, ok := m.OnlineStatusLog[target.ID]
  89. if !ok {
  90. //First record. Create the array
  91. m.OnlineStatusLog[target.ID] = []*Record{&thisRecord}
  92. } else {
  93. //Append to the previous record
  94. thisRecords = append(thisRecords, &thisRecord)
  95. //Check if the record is longer than the logged record. If yes, clear out the old records
  96. if len(thisRecords) > m.Config.MaxRecordsStore {
  97. thisRecords = thisRecords[1:]
  98. }
  99. m.OnlineStatusLog[target.ID] = thisRecords
  100. }
  101. }
  102. //TODO: Write results to db
  103. }
  104. func (m *Monitor) AddTargetToMonitor(target *Target) {
  105. // Add target to Config
  106. m.Config.Targets = append(m.Config.Targets, target)
  107. // Add target to OnlineStatusLog
  108. m.OnlineStatusLog[target.ID] = []*Record{}
  109. }
  110. func (m *Monitor) RemoveTargetFromMonitor(targetId string) {
  111. // Remove target from Config
  112. for i, target := range m.Config.Targets {
  113. if target.ID == targetId {
  114. m.Config.Targets = append(m.Config.Targets[:i], m.Config.Targets[i+1:]...)
  115. break
  116. }
  117. }
  118. // Remove target from OnlineStatusLog
  119. delete(m.OnlineStatusLog, targetId)
  120. }
  121. // Scan the config target. If a target exists in m.OnlineStatusLog no longer
  122. // exists in m.Monitor.Config.Targets, it remove it from the log as well.
  123. func (m *Monitor) CleanRecords() {
  124. // Create a set of IDs for all targets in the config
  125. targetIDs := make(map[string]bool)
  126. for _, target := range m.Config.Targets {
  127. targetIDs[target.ID] = true
  128. }
  129. // Iterate over all log entries and remove any that have a target ID that
  130. // is not in the set of current target IDs
  131. newStatusLog := m.OnlineStatusLog
  132. for id, _ := range m.OnlineStatusLog {
  133. _, idExistsInTargets := targetIDs[id]
  134. if !idExistsInTargets {
  135. delete(newStatusLog, id)
  136. }
  137. }
  138. m.OnlineStatusLog = newStatusLog
  139. }
  140. /*
  141. Web Interface Handler
  142. */
  143. func (m *Monitor) HandleUptimeLogRead(w http.ResponseWriter, r *http.Request) {
  144. id, _ := utils.GetPara(r, "id")
  145. if id == "" {
  146. js, _ := json.Marshal(m.OnlineStatusLog)
  147. w.Header().Set("Content-Type", "application/json")
  148. w.Write(js)
  149. } else {
  150. //Check if that id exists
  151. log, ok := m.OnlineStatusLog[id]
  152. if !ok {
  153. http.NotFound(w, r)
  154. return
  155. }
  156. js, _ := json.MarshalIndent(log, "", " ")
  157. w.Header().Set("Content-Type", "application/json")
  158. w.Write(js)
  159. }
  160. }
  161. /*
  162. Utilities
  163. */
  164. // Get website stauts with latency given URL, return is conn succ and its latency and status code
  165. func getWebsiteStatusWithLatency(url string) (bool, int64, int) {
  166. start := time.Now().UnixNano() / int64(time.Millisecond)
  167. statusCode, err := getWebsiteStatus(url)
  168. end := time.Now().UnixNano() / int64(time.Millisecond)
  169. if err != nil {
  170. log.Println(err.Error())
  171. return false, 0, 0
  172. } else {
  173. diff := end - start
  174. succ := false
  175. if statusCode >= 200 && statusCode < 300 {
  176. //OK
  177. succ = true
  178. } else if statusCode >= 300 && statusCode < 400 {
  179. //Redirection code
  180. succ = true
  181. } else {
  182. succ = false
  183. }
  184. return succ, diff, statusCode
  185. }
  186. }
  187. func getWebsiteStatus(url string) (int, error) {
  188. resp, err := http.Get(url)
  189. if err != nil {
  190. //Try replace the http with https and vise versa
  191. rewriteURL := ""
  192. if strings.Contains(url, "https://") {
  193. rewriteURL = strings.ReplaceAll(url, "https://", "http://")
  194. } else if strings.Contains(url, "http://") {
  195. rewriteURL = strings.ReplaceAll(url, "http://", "https://")
  196. }
  197. resp, err = http.Get(rewriteURL)
  198. if err != nil {
  199. if strings.Contains(err.Error(), "http: server gave HTTP response to HTTPS client") {
  200. //Invalid downstream reverse proxy settings, but it is online
  201. //return SSL handshake failed
  202. return 525, nil
  203. }
  204. return 0, err
  205. }
  206. }
  207. status_code := resp.StatusCode
  208. resp.Body.Close()
  209. return status_code, nil
  210. }