You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

430 line
12 KiB

  1. package main
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "log"
  7. "net/http"
  8. "os/signal"
  9. "strings"
  10. "sync"
  11. "syscall"
  12. "time"
  13. "github.com/AFASystems/presence/internal/pkg/common/appcontext"
  14. "github.com/AFASystems/presence/internal/pkg/config"
  15. "github.com/AFASystems/presence/internal/pkg/kafkaclient"
  16. "github.com/AFASystems/presence/internal/pkg/model"
  17. "github.com/gorilla/handlers"
  18. "github.com/gorilla/mux"
  19. "github.com/gorilla/websocket"
  20. "github.com/redis/go-redis/v9"
  21. "github.com/segmentio/kafka-go"
  22. )
  23. var upgrader = websocket.Upgrader{
  24. CheckOrigin: func(r *http.Request) bool { return true },
  25. }
  26. var wg sync.WaitGroup
  27. func main() {
  28. cfg := config.Load()
  29. appState := appcontext.NewAppState()
  30. // define context
  31. ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
  32. defer stop()
  33. headersOk := handlers.AllowedHeaders([]string{"X-Requested-With"})
  34. originsOk := handlers.AllowedOrigins([]string{"*"})
  35. methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"})
  36. writer := appState.AddKafkaWriter(cfg.KafkaURL, "apibeacons")
  37. settingsWriter := appState.AddKafkaWriter(cfg.KafkaURL, "settings")
  38. locationReader := appState.AddKafkaReader(cfg.KafkaURL, "locevents", "gid-loc-server")
  39. alertsReader := appState.AddKafkaReader(cfg.KafkaURL, "alertbeacons", "gid-alert-serv")
  40. client := redis.NewClient(&redis.Options{
  41. Addr: "127.0.0.1:6379",
  42. Password: "",
  43. })
  44. // Separate channel for location change?
  45. chLoc := make(chan model.HTTPLocation, 200)
  46. chEvents := make(chan model.BeaconEvent, 500)
  47. wg.Add(2)
  48. go kafkaclient.Consume(locationReader, chLoc, ctx, &wg)
  49. go kafkaclient.Consume(alertsReader, chEvents, ctx, &wg)
  50. r := mux.NewRouter()
  51. // declare WS clients list | do I need it though? or will locations worker send message
  52. // to kafka and then only this service (server) is being used for communication with the clients
  53. clients := make(map[*websocket.Conn]bool)
  54. // Declare broadcast channel
  55. broadcast := make(chan model.Message)
  56. // For now just add beacon DELETE / GET / POST / PUT methods
  57. r.HandleFunc("/api/beacons/{beacon_id}", beaconsDeleteHandler(writer)).Methods("DELETE")
  58. r.HandleFunc("/api/beacons", beaconsListHandler(appState)).Methods("GET")
  59. r.HandleFunc("/api/beacons/{beacon_id}", beaconsListSingleHandler(appState)).Methods("GET")
  60. r.HandleFunc("/api/beacons", beaconsAddHandler(writer)).Methods("POST")
  61. r.HandleFunc("/api/beacons", beaconsAddHandler(writer)).Methods("PUT")
  62. // r.HandleFunc("/api/settings", settingsListHandler(client)).Methods("GET")
  63. r.HandleFunc("/api/settings", settingsEditHandler(settingsWriter)).Methods("POST")
  64. // Handler for WS messages
  65. // No point in having seperate route for each message type, better to handle different message types in one connection
  66. // r.HandleFunc("/ws/api/beacons", serveWs(client))
  67. // r.HandleFunc("/ws/api/beacons/latest", serveLatestBeaconsWs(client))
  68. r.HandleFunc("/ws/broadcast", handleConnections(clients, broadcast))
  69. http.ListenAndServe("0.0.0.0:1902", handlers.CORS(originsOk, headersOk, methodsOk)(r))
  70. eventLoop:
  71. for {
  72. select {
  73. case <-ctx.Done():
  74. break eventLoop
  75. case msg := <-chLoc:
  76. beacon, ok := appState.GetBeacon(msg.ID)
  77. if !ok {
  78. appState.UpdateBeacon(msg.ID, model.Beacon{ID: msg.ID, Location: msg.Location, Distance: msg.Distance, LastSeen: msg.LastSeen, PreviousConfidentLocation: msg.PreviousConfidentLocation})
  79. } else {
  80. beacon.ID = msg.ID
  81. beacon.Location = msg.Location
  82. beacon.Distance = msg.Distance
  83. beacon.LastSeen = msg.LastSeen
  84. beacon.PreviousConfidentLocation = msg.PreviousConfidentLocation
  85. appState.UpdateBeacon(msg.ID, beacon)
  86. }
  87. key := fmt.Sprintf("beacon:%s", msg.ID)
  88. hashM, err := msg.RedisHashable()
  89. if err != nil {
  90. fmt.Println("Error in converting location into hashmap for Redis insert: ", err)
  91. continue
  92. }
  93. if err := client.HSet(ctx, key, hashM).Err(); err != nil {
  94. fmt.Println("Error in persisting set in Redis key: ", key)
  95. continue
  96. }
  97. if err := client.SAdd(ctx, "beacons", key).Err(); err != nil {
  98. fmt.Println("Error in adding beacon to the beacons list for get all operation: ", err)
  99. }
  100. case msg := <-chEvents:
  101. beacon, ok := appState.GetBeacon(msg.ID)
  102. if !ok {
  103. appState.UpdateBeacon(msg.ID, model.Beacon{ID: msg.ID, BeaconType: msg.Type, HSBattery: int64(msg.Battery), Event: msg.Event})
  104. } else {
  105. beacon.ID = msg.ID
  106. beacon.BeaconType = msg.Type
  107. beacon.HSBattery = int64(msg.Battery)
  108. beacon.Event = msg.Event
  109. appState.UpdateBeacon(msg.ID, beacon)
  110. }
  111. key := fmt.Sprintf("beacon:%s", msg.ID)
  112. hashM, err := msg.RedisHashable()
  113. if err != nil {
  114. fmt.Println("Error in converting location into hashmap for Redis insert: ", err)
  115. continue
  116. }
  117. if err := client.HSet(ctx, key, hashM).Err(); err != nil {
  118. fmt.Println("Error in persisting set in Redis key: ", key)
  119. continue
  120. }
  121. if err := client.SAdd(ctx, "beacons", key).Err(); err != nil {
  122. fmt.Println("Error in adding beacon to the beacons list for get all operation: ", err)
  123. }
  124. }
  125. }
  126. fmt.Println("broken out of the main event loop")
  127. wg.Wait()
  128. fmt.Println("All go routines have stopped, Beggining to close Kafka connections")
  129. appState.CleanKafkaReaders()
  130. appState.CleanKafkaWriters()
  131. }
  132. func beaconsListSingleHandler(appstate *appcontext.AppState) http.HandlerFunc {
  133. return func(w http.ResponseWriter, r *http.Request) {
  134. vars := mux.Vars(r)
  135. id := vars["beacon_id"]
  136. beacon, ok := appstate.GetBeacon(id)
  137. if !ok {
  138. w.Header().Set("Content-Type", "application/json")
  139. w.WriteHeader(http.StatusNotFound)
  140. json.NewEncoder(w).Encode(map[string]string{"error": "Beacon not found"})
  141. return
  142. }
  143. w.Header().Set("Content-Type", "application/json")
  144. w.WriteHeader(http.StatusOK)
  145. json.NewEncoder(w).Encode(beacon)
  146. }
  147. }
  148. func beaconsListHandler(appstate *appcontext.AppState) http.HandlerFunc {
  149. return func(w http.ResponseWriter, r *http.Request) {
  150. beacons := appstate.GetAllBeacons()
  151. w.Header().Set("Content-Type", "application/json")
  152. w.WriteHeader(http.StatusOK)
  153. json.NewEncoder(w).Encode(beacons)
  154. }
  155. }
  156. // Probably define value as interface and then reuse this writer in all of the functions
  157. func sendKafkaMessage(writer *kafka.Writer, value *model.ApiUpdate) bool {
  158. valueStr, err := json.Marshal(&value)
  159. if err != nil {
  160. fmt.Println("error in encoding: ", err)
  161. return false
  162. }
  163. msg := kafka.Message{
  164. Value: valueStr,
  165. }
  166. err = writer.WriteMessages(context.Background(), msg)
  167. if err != nil {
  168. fmt.Println("Error in sending kafka message: ")
  169. return false
  170. }
  171. return true
  172. }
  173. func beaconsDeleteHandler(writer *kafka.Writer) http.HandlerFunc {
  174. return func(w http.ResponseWriter, r *http.Request) {
  175. vars := mux.Vars(r)
  176. beaconId := vars["beacon_id"]
  177. apiUpdate := model.ApiUpdate{
  178. Method: "DELETE",
  179. ID: beaconId,
  180. }
  181. fmt.Println("Sending DELETE message")
  182. flag := sendKafkaMessage(writer, &apiUpdate)
  183. if !flag {
  184. fmt.Println("error in sending Kafka message")
  185. http.Error(w, "Error in sending kafka message", 500)
  186. return
  187. }
  188. w.Write([]byte("ok"))
  189. }
  190. }
  191. func beaconsAddHandler(writer *kafka.Writer) http.HandlerFunc {
  192. return func(w http.ResponseWriter, r *http.Request) {
  193. decoder := json.NewDecoder(r.Body)
  194. var inBeacon model.Beacon
  195. err := decoder.Decode(&inBeacon)
  196. if err != nil {
  197. http.Error(w, err.Error(), 400)
  198. return
  199. }
  200. fmt.Println("sending POST message")
  201. if (len(strings.TrimSpace(inBeacon.Name)) == 0) || (len(strings.TrimSpace(inBeacon.ID)) == 0) {
  202. http.Error(w, "name and beacon_id cannot be blank", 400)
  203. return
  204. }
  205. apiUpdate := model.ApiUpdate{
  206. Method: "POST",
  207. Beacon: inBeacon,
  208. }
  209. flag := sendKafkaMessage(writer, &apiUpdate)
  210. if !flag {
  211. fmt.Println("error in sending Kafka message")
  212. http.Error(w, "Error in sending kafka message", 500)
  213. return
  214. }
  215. w.Write([]byte("ok"))
  216. }
  217. }
  218. func settingsEditHandler(writer *kafka.Writer) http.HandlerFunc {
  219. return func(w http.ResponseWriter, r *http.Request) {
  220. decoder := json.NewDecoder(r.Body)
  221. var inSettings model.SettingsVal
  222. if err := decoder.Decode(&inSettings); err != nil {
  223. http.Error(w, err.Error(), 400)
  224. fmt.Println("Error in decoding Settings body: ", err)
  225. return
  226. }
  227. if !settingsCheck(inSettings) {
  228. http.Error(w, "values must be greater than 0", 400)
  229. fmt.Println("settings values must be greater than 0")
  230. return
  231. }
  232. valueStr, err := json.Marshal(&inSettings)
  233. if err != nil {
  234. http.Error(w, "Error in encoding settings", 500)
  235. fmt.Println("Error in encoding settings: ", err)
  236. return
  237. }
  238. msg := kafka.Message{
  239. Value: valueStr,
  240. }
  241. if err := writer.WriteMessages(context.Background(), msg); err != nil {
  242. fmt.Println("error in sending Kafka message")
  243. http.Error(w, "Error in sending kafka message", 500)
  244. return
  245. }
  246. w.Write([]byte("ok"))
  247. }
  248. }
  249. func settingsCheck(settings model.SettingsVal) bool {
  250. if settings.LocationConfidence <= 0 || settings.LastSeenThreshold <= 0 || settings.HASendInterval <= 0 {
  251. return false
  252. }
  253. return true
  254. }
  255. func serveWs(client *redis.Client) http.HandlerFunc {
  256. return func(w http.ResponseWriter, r *http.Request) {
  257. ws, err := upgrader.Upgrade(w, r, nil)
  258. if err != nil {
  259. if _, ok := err.(websocket.HandshakeError); !ok {
  260. log.Println(err)
  261. }
  262. return
  263. }
  264. go writer(ws, client)
  265. reader(ws)
  266. }
  267. }
  268. func writer(ws *websocket.Conn, client *redis.Client) {
  269. pingTicker := time.NewTicker((60 * time.Second * 9) / 10)
  270. beaconTicker := time.NewTicker(2 * time.Second)
  271. defer func() {
  272. pingTicker.Stop()
  273. beaconTicker.Stop()
  274. ws.Close()
  275. }()
  276. for {
  277. select {
  278. case <-beaconTicker.C:
  279. httpresults, err := client.Get(context.Background(), "httpresults").Result()
  280. if err == redis.Nil {
  281. fmt.Println("no beacons list, starting empty")
  282. } else if err != nil {
  283. panic(err)
  284. } else {
  285. ws.SetWriteDeadline(time.Now().Add(10 * time.Second))
  286. if err := ws.WriteMessage(websocket.TextMessage, []byte(httpresults)); err != nil {
  287. return
  288. }
  289. }
  290. case <-pingTicker.C:
  291. ws.SetWriteDeadline(time.Now().Add(10 * time.Second))
  292. if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
  293. return
  294. }
  295. }
  296. }
  297. }
  298. func serveLatestBeaconsWs(client *redis.Client) http.HandlerFunc {
  299. return func(w http.ResponseWriter, r *http.Request) {
  300. ws, err := upgrader.Upgrade(w, r, nil)
  301. if err != nil {
  302. if _, ok := err.(websocket.HandshakeError); !ok {
  303. log.Println(err)
  304. }
  305. return
  306. }
  307. go latestBeaconWriter(ws, client)
  308. reader(ws)
  309. }
  310. }
  311. // This and writer can be refactored in one function
  312. func latestBeaconWriter(ws *websocket.Conn, client *redis.Client) {
  313. pingTicker := time.NewTicker((60 * time.Second * 9) / 10)
  314. beaconTicker := time.NewTicker(2 * time.Second)
  315. defer func() {
  316. pingTicker.Stop()
  317. beaconTicker.Stop()
  318. ws.Close()
  319. }()
  320. for {
  321. select {
  322. case <-beaconTicker.C:
  323. latestbeacons, err := client.Get(context.Background(), "latestbeacons").Result()
  324. if err == redis.Nil {
  325. fmt.Println("no beacons list, starting empty")
  326. } else if err != nil {
  327. panic(err)
  328. } else {
  329. ws.SetWriteDeadline(time.Now().Add(10 * time.Second))
  330. if err := ws.WriteMessage(websocket.TextMessage, []byte(latestbeacons)); err != nil {
  331. return
  332. }
  333. }
  334. case <-pingTicker.C:
  335. ws.SetWriteDeadline(time.Now().Add(10 * time.Second))
  336. if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
  337. return
  338. }
  339. }
  340. }
  341. }
  342. func reader(ws *websocket.Conn) {
  343. defer ws.Close()
  344. ws.SetReadLimit(512)
  345. ws.SetReadDeadline(time.Now().Add(60 * time.Second))
  346. ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(60 * time.Second)); return nil })
  347. for {
  348. _, _, err := ws.ReadMessage()
  349. if err != nil {
  350. break
  351. }
  352. }
  353. }
  354. func handleConnections(clients map[*websocket.Conn]bool, broadcast chan model.Message) http.HandlerFunc {
  355. return func(w http.ResponseWriter, r *http.Request) {
  356. ws, err := upgrader.Upgrade(w, r, nil)
  357. if err != nil {
  358. log.Fatal(err)
  359. }
  360. defer ws.Close()
  361. clients[ws] = true
  362. for {
  363. var msg model.Message
  364. err := ws.ReadJSON(&msg)
  365. if err != nil {
  366. log.Printf("error: %v", err)
  367. delete(clients, ws)
  368. break
  369. }
  370. broadcast <- msg
  371. }
  372. }
  373. }