Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 

295 řádky
9.1 KiB

  1. package main
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "log"
  8. "log/slog"
  9. "net/http"
  10. "os"
  11. "os/signal"
  12. "strings"
  13. "sync"
  14. "syscall"
  15. "time"
  16. "github.com/AFASystems/presence/internal/pkg/apiclient"
  17. "github.com/AFASystems/presence/internal/pkg/common/appcontext"
  18. "github.com/AFASystems/presence/internal/pkg/config"
  19. "github.com/AFASystems/presence/internal/pkg/controller"
  20. "github.com/AFASystems/presence/internal/pkg/database"
  21. "github.com/AFASystems/presence/internal/pkg/kafkaclient"
  22. "github.com/AFASystems/presence/internal/pkg/model"
  23. "github.com/AFASystems/presence/internal/pkg/service"
  24. "github.com/gorilla/handlers"
  25. "github.com/gorilla/mux"
  26. "github.com/gorilla/websocket"
  27. "github.com/segmentio/kafka-go"
  28. "gorm.io/gorm"
  29. )
  30. var upgrader = websocket.Upgrader{
  31. ReadBufferSize: 1024,
  32. WriteBufferSize: 1024,
  33. }
  34. var _ io.Writer = (*os.File)(nil)
  35. var wg sync.WaitGroup
  36. func main() {
  37. cfg := config.Load()
  38. appState := appcontext.NewAppState()
  39. // Create log file
  40. logFile, err := os.OpenFile("server.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
  41. if err != nil {
  42. log.Fatalf("Failed to open log file: %v\n", err)
  43. }
  44. // shell and log file multiwriter
  45. w := io.MultiWriter(os.Stderr, logFile)
  46. logger := slog.New(slog.NewJSONHandler(w, nil))
  47. slog.SetDefault(logger)
  48. // define context
  49. ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
  50. defer stop()
  51. db, err := database.Connect(cfg)
  52. if err != nil {
  53. log.Fatalf("Failed to open database connection: %v\n", err)
  54. }
  55. headersOk := handlers.AllowedHeaders([]string{"X-Requested-With"})
  56. originsOk := handlers.AllowedOrigins([]string{"*"})
  57. methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"})
  58. writer := appState.AddKafkaWriter(cfg.KafkaURL, "apibeacons")
  59. settingsWriter := appState.AddKafkaWriter(cfg.KafkaURL, "settings")
  60. alertWriter := appState.AddKafkaWriter(cfg.KafkaURL, "alert")
  61. parserWriter := appState.AddKafkaWriter(cfg.KafkaURL, "parser")
  62. mqttWriter := appState.AddKafkaWriter(cfg.KafkaURL, "mqtt")
  63. slog.Info("Kafka writers topics: apibeacons, settings initialized")
  64. configFile, err := os.Open("/app/cmd/server/config.json")
  65. if err != nil {
  66. panic(err)
  67. }
  68. b, _ := io.ReadAll(configFile)
  69. var configs []model.Config
  70. json.Unmarshal(b, &configs)
  71. for _, config := range configs {
  72. // persist read configs in database
  73. db.Create(&config)
  74. }
  75. db.Find(&configs)
  76. for _, config := range configs {
  77. kp := model.KafkaParser{
  78. ID: "add",
  79. Config: config,
  80. }
  81. if err := service.SendParserConfig(kp, parserWriter, ctx); err != nil {
  82. fmt.Printf("Unable to send parser config to kafka broker %v\n", err)
  83. }
  84. }
  85. if err := apiclient.UpdateDB(db, ctx, cfg, writer, appState); err != nil {
  86. fmt.Printf("Error in getting token: %v\n", err)
  87. }
  88. locationReader := appState.AddKafkaReader(cfg.KafkaURL, "locevents", "gid-loc-server")
  89. alertsReader := appState.AddKafkaReader(cfg.KafkaURL, "alertbeacons", "gid-alert-serv")
  90. slog.Info("Kafka readers topics: locevents, alertbeacons initialized")
  91. chLoc := make(chan model.HTTPLocation, 200)
  92. chEvents := make(chan model.BeaconEvent, 500)
  93. wg.Add(2)
  94. go kafkaclient.Consume(locationReader, chLoc, ctx, &wg)
  95. go kafkaclient.Consume(alertsReader, chEvents, ctx, &wg)
  96. r := mux.NewRouter()
  97. r.HandleFunc("/reslevis/getGateways", controller.GatewayListController(db)).Methods("GET")
  98. r.HandleFunc("/reslevis/postGateway", controller.GatewayAddController(db)).Methods("POST")
  99. r.HandleFunc("/reslevis/removeGateway/{id}", controller.GatewayDeleteController(db)).Methods("DELETE")
  100. r.HandleFunc("/reslevis/updateGateway/{id}", controller.GatewayUpdateController(db)).Methods("PUT")
  101. r.HandleFunc("/reslevis/getZones", controller.ZoneListController(db)).Methods("GET")
  102. r.HandleFunc("/reslevis/postZone", controller.ZoneAddController(db)).Methods("POST")
  103. r.HandleFunc("/reslevis/removeZone/{id}", controller.ZoneDeleteController(db)).Methods("DELETE")
  104. r.HandleFunc("/reslevis/updateZone", controller.ZoneUpdateController(db)).Methods("PUT")
  105. r.HandleFunc("/reslevis/getTrackerZones", controller.TrackerZoneListController(db)).Methods("GET")
  106. r.HandleFunc("/reslevis/postTrackerZone", controller.TrackerZoneAddController(db)).Methods("POST")
  107. r.HandleFunc("/reslevis/removeTrackerZone/{id}", controller.TrackerZoneDeleteController(db)).Methods("DELETE")
  108. r.HandleFunc("/reslevis/updateTrackerZone", controller.TrackerZoneUpdateController(db)).Methods("PUT")
  109. r.HandleFunc("/reslevis/getTrackers", controller.TrackerList(db)).Methods("GET")
  110. r.HandleFunc("/reslevis/postTracker", controller.TrackerAdd(db, writer, ctx)).Methods("POST")
  111. r.HandleFunc("/reslevis/removeTracker/{id}", controller.TrackerDelete(db, writer, ctx)).Methods("DELETE")
  112. r.HandleFunc("/reslevis/updateTracker", controller.TrackerUpdate(db)).Methods("PUT")
  113. r.HandleFunc("/configs/beacons", controller.ParserListController(db)).Methods("GET")
  114. r.HandleFunc("/configs/beacons", controller.ParserAddController(db, parserWriter, ctx)).Methods("POST")
  115. r.HandleFunc("/configs/beacons/{id}", controller.ParserUpdateController(db, parserWriter, ctx)).Methods("PUT")
  116. r.HandleFunc("/configs/beacons/{id}", controller.ParserDeleteController(db, parserWriter, ctx)).Methods("DELETE")
  117. r.HandleFunc("/reslevis/settings", controller.SettingsUpdateController(db, settingsWriter, ctx)).Methods("PATCH")
  118. r.HandleFunc("/reslevis/settings", controller.SettingsListController(db)).Methods("GET")
  119. beaconTicker := time.NewTicker(2 * time.Second)
  120. wsHandler := http.HandlerFunc(serveWs(db, ctx))
  121. restApiHandler := handlers.CORS(originsOk, headersOk, methodsOk)(r)
  122. mainHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  123. if strings.HasPrefix(r.URL.Path, "/api/beacons/ws") {
  124. wsHandler.ServeHTTP(w, r)
  125. return
  126. }
  127. restApiHandler.ServeHTTP(w, r)
  128. })
  129. server := http.Server{
  130. Addr: cfg.HTTPAddr,
  131. Handler: mainHandler,
  132. }
  133. go server.ListenAndServe()
  134. eventLoop:
  135. for {
  136. select {
  137. case <-ctx.Done():
  138. break eventLoop
  139. case msg := <-chLoc:
  140. service.LocationToBeaconService(msg, db, alertWriter, ctx)
  141. case msg := <-chEvents:
  142. fmt.Printf("event: %+v\n", msg)
  143. id := msg.ID
  144. if err := db.First(&model.Tracker{}, "id = ?", id).Error; err != nil {
  145. fmt.Printf("Decoder event for untracked beacon: %s\n", id)
  146. continue
  147. }
  148. if err := db.Updates(&model.Tracker{ID: id, Battery: msg.Battery}).Error; err != nil {
  149. fmt.Printf("Error in saving decoder event for beacon: %s\n", id)
  150. continue
  151. }
  152. case <-beaconTicker.C:
  153. var list []model.Tracker
  154. db.Find(&list)
  155. eMsg, err := json.Marshal(list)
  156. if err != nil {
  157. fmt.Printf("Error in marshaling trackers list: %v\n", err)
  158. continue
  159. }
  160. msg := kafka.Message{
  161. Value: eMsg,
  162. }
  163. mqttWriter.WriteMessages(ctx, msg)
  164. }
  165. }
  166. if err := server.Shutdown(context.Background()); err != nil {
  167. eMsg := fmt.Sprintf("could not shutdown: %v\n", err)
  168. slog.Error(eMsg)
  169. }
  170. slog.Info("API SERVER: \n")
  171. slog.Warn("broken out of the main event loop and HTTP server shutdown\n")
  172. wg.Wait()
  173. slog.Info("All go routines have stopped, Beggining to close Kafka connections\n")
  174. appState.CleanKafkaReaders()
  175. appState.CleanKafkaWriters()
  176. slog.Info("All kafka clients shutdown, starting shutdown of valkey client")
  177. slog.Info("API server shutting down")
  178. logFile.Close()
  179. }
  180. func serveWs(db *gorm.DB, ctx context.Context) http.HandlerFunc {
  181. return func(w http.ResponseWriter, r *http.Request) {
  182. ws, err := upgrader.Upgrade(w, r, nil)
  183. if err != nil {
  184. if _, ok := err.(websocket.HandshakeError); !ok {
  185. eMsg := fmt.Sprintf("could not upgrade ws connection: %v\n", err)
  186. slog.Error(eMsg)
  187. }
  188. return
  189. }
  190. wg.Add(2)
  191. go writer(ws, db, ctx)
  192. go reader(ws, ctx)
  193. }
  194. }
  195. func writer(ws *websocket.Conn, db *gorm.DB, ctx context.Context) {
  196. pingTicker := time.NewTicker((60 * 9) / 10 * time.Second)
  197. beaconTicker := time.NewTicker(2 * time.Second)
  198. defer func() {
  199. pingTicker.Stop()
  200. beaconTicker.Stop()
  201. ws.Close()
  202. wg.Done()
  203. }()
  204. for {
  205. select {
  206. case <-ctx.Done():
  207. slog.Info("WebSocket writer received shutdown signal.")
  208. ws.SetWriteDeadline(time.Now().Add(10 * time.Second))
  209. ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
  210. return
  211. case <-beaconTicker.C:
  212. var list []model.Tracker
  213. db.Find(&list)
  214. js, err := json.Marshal(list)
  215. if err != nil {
  216. js = []byte("error")
  217. }
  218. ws.SetWriteDeadline(time.Now().Add(10 * time.Second))
  219. if err := ws.WriteMessage(websocket.TextMessage, js); err != nil {
  220. return
  221. }
  222. case <-pingTicker.C:
  223. ws.SetWriteDeadline(time.Now().Add(10 * time.Second))
  224. if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
  225. return
  226. }
  227. }
  228. }
  229. }
  230. func reader(ws *websocket.Conn, ctx context.Context) {
  231. defer func() {
  232. ws.Close()
  233. wg.Done()
  234. }()
  235. ws.SetReadLimit(512)
  236. ws.SetReadDeadline(time.Now().Add((60 * 9) / 10 * time.Second))
  237. ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add((60 * 9) / 10 * time.Second)); return nil })
  238. for {
  239. select {
  240. case <-ctx.Done():
  241. slog.Info("closing ws reader")
  242. return
  243. default:
  244. _, _, err := ws.ReadMessage()
  245. if err != nil {
  246. return
  247. }
  248. }
  249. }
  250. }