|
- package httpserver
-
- import (
- "encoding/json"
- "fmt"
- "log"
- "net/http"
- "strings"
- "sync"
- "time"
-
- "github.com/AFASystems/presence/internal/pkg/model"
- "github.com/AFASystems/presence/internal/pkg/persistence"
- "github.com/gorilla/handlers"
- "github.com/gorilla/mux"
- "github.com/gorilla/websocket"
- )
-
- var (
- upgrader = websocket.Upgrader{
- ReadBufferSize: 1024,
- WriteBufferSize: 1024,
- CheckOrigin: func(r *http.Request) bool {
- return true
- },
- }
- )
-
- const (
- writeWait = 10 * time.Second
- pongWait = 60 * time.Second
- pingPeriod = (pongWait * 9) / 10
- beaconPeriod = 2 * time.Second
- )
-
- // Init store in main or anywhere else and pass it to all initializer functions
- // called in main, then with controllers or handlers use wrapper that takes entire store
- // allocates only the properties that need to be passed into the controller
-
- func StartHTTPServer(addr string, ctx *model.AppContext) {
- headersOk := handlers.AllowedHeaders([]string{"X-Requested-With"})
- originsOk := handlers.AllowedOrigins([]string{"*"})
- methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"})
-
- // Set up HTTP server
- r := mux.NewRouter()
- r.HandleFunc("/api/results", resultsHandler(&ctx.HTTPResults))
-
- r.HandleFunc("/api/beacons/{beacon_id}", BeaconsDeleteHandler(&ctx.Beacons, ctx.ButtonsList)).Methods("DELETE")
- r.HandleFunc("/api/beacons", BeaconsListHandler(&ctx.Beacons)).Methods("GET")
- r.HandleFunc("/api/beacons", BeaconsAddHandler(&ctx.Beacons)).Methods("POST") //since beacons are hashmap, just have put and post be same thing. it'll either add or modify that entry
- r.HandleFunc("/api/beacons", BeaconsAddHandler(&ctx.Beacons)).Methods("PUT")
-
- r.HandleFunc("/api/latest-beacons", latestBeaconsListHandler(&ctx.LatestList)).Methods("GET")
-
- r.HandleFunc("/api/settings", SettingsListHandler(&ctx.Settings)).Methods("GET")
- r.HandleFunc("/api/settings", SettingsEditHandler(&ctx.Settings)).Methods("POST")
-
- r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(http.Dir("static_html/js/"))))
- r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(http.Dir("static_html/css/"))))
- r.PathPrefix("/img/").Handler(http.StripPrefix("/img/", http.FileServer(http.Dir("static_html/img/"))))
- r.PathPrefix("/").Handler(http.FileServer(http.Dir("static_html/")))
-
- http.Handle("/", r)
-
- mxWS := mux.NewRouter()
- mxWS.HandleFunc("/ws/api/beacons", serveWs(&ctx.HTTPResults))
- mxWS.HandleFunc("/ws/api/beacons/latest", serveLatestBeaconsWs(&ctx.LatestList))
- mxWS.HandleFunc("/ws/broadcast", handleConnections(ctx.Clients, &ctx.Broadcast))
- http.Handle("/ws/", mxWS)
-
- go handleMessages(ctx.Clients, &ctx.Broadcast)
-
- go func() {
- log.Fatal(http.ListenAndServe(*model.HTTPWSHostPathPtr, nil))
- }()
-
- http.ListenAndServe(*model.HTTPHostPathPtr, handlers.CORS(originsOk, headersOk, methodsOk)(r))
-
- }
-
- // TODO: rather add defer to unlock the files
-
- func resultsHandler(httpResults *model.HTTPResultsList) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- httpResults.HTTPResultsLock.Lock()
- defer httpResults.HTTPResultsLock.Unlock()
- js, err := json.Marshal(httpResults.HTTPResults)
-
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- w.Write(js)
- }
- }
-
- func BeaconsListHandler(beacons *model.BeaconsList) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- beacons.Lock.RLock()
- js, err := json.Marshal(beacons.Beacons)
- beacons.Lock.RUnlock()
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- w.Write(js)
- }
- }
-
- func BeaconsAddHandler(beacons *model.BeaconsList) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- decoder := json.NewDecoder(r.Body)
- var inBeacon model.Beacon
- err := decoder.Decode(&inBeacon)
-
- if err != nil {
- http.Error(w, err.Error(), 400)
- return
- }
-
- if (len(strings.TrimSpace(inBeacon.Name)) == 0) || (len(strings.TrimSpace(inBeacon.Beacon_id)) == 0) {
- http.Error(w, "name and beacon_id cannot be blank", 400)
- return
- }
-
- beacons.Beacons[inBeacon.Beacon_id] = inBeacon
-
- err = persistence.PersistBeacons(beacons)
-
- if err != nil {
- http.Error(w, "trouble persisting beacons list, create bucket", 500)
- return
- }
-
- w.Write([]byte("ok"))
- }
- }
-
- func BeaconsDeleteHandler(beacons *model.BeaconsList, buttonsList map[string]model.Button) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- fmt.Println("route param: ", vars)
- beaconId := vars["beacon_id"]
- _, ok := beacons.Beacons[beaconId]
- if !ok {
- http.Error(w, "no beacon with the specified id", 400) // change the status code
- return
- }
- delete(beacons.Beacons, beaconId)
-
- _, ok = buttonsList[beaconId]
- if ok {
- delete(buttonsList, beaconId)
- }
-
- err := persistence.PersistBeacons(beacons)
- if err != nil {
- http.Error(w, "trouble persisting beacons list, create bucket", 500)
- return
- }
-
- w.Write([]byte("ok"))
- }
- }
-
- func latestBeaconsListHandler(latestList *model.LatestBeaconsList) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- latestList.LatestListLock.RLock()
- var la = make([]model.Beacon, 0)
- for _, b := range latestList.LatestList {
- la = append(la, b)
- }
- latestList.LatestListLock.RUnlock()
- js, err := json.Marshal(la)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- w.Write(js)
- }
- }
-
- func SettingsListHandler(settings *model.Settings) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- js, err := json.Marshal(settings)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- w.Write(js)
- }
- }
-
- func SettingsEditHandler(settings *model.Settings) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- decoder := json.NewDecoder(r.Body)
- var inSettings model.Settings
- err := decoder.Decode(&inSettings)
- if err != nil {
- http.Error(w, err.Error(), 400)
- return
- }
-
- //make sure values are > 0
- if (inSettings.Location_confidence <= 0) ||
- (inSettings.Last_seen_threshold <= 0) ||
- (inSettings.HA_send_interval <= 0) {
- http.Error(w, "values must be greater than 0", 400)
- return
- }
-
- *settings = inSettings
-
- err = persistence.PersistSettings(settings)
- if err != nil {
- http.Error(w, "trouble persisting settings, create bucket", 500)
- return
- }
-
- w.Write([]byte("ok"))
- }
- }
-
- func reader(ws *websocket.Conn) {
- defer ws.Close()
- ws.SetReadLimit(512)
- ws.SetReadDeadline(time.Now().Add(pongWait))
- ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
- for {
- _, _, err := ws.ReadMessage()
- if err != nil {
- break
- }
- }
- }
-
- func writer(ws *websocket.Conn, httpResult *model.HTTPResultsList) {
- pingTicker := time.NewTicker(pingPeriod)
- beaconTicker := time.NewTicker(beaconPeriod)
- defer func() {
- pingTicker.Stop()
- beaconTicker.Stop()
- ws.Close()
- }()
- for {
- select {
- case <-beaconTicker.C:
- httpResult.HTTPResultsLock.Lock()
- defer httpResult.HTTPResultsLock.Unlock()
- js, err := json.Marshal(httpResult.HTTPResults)
-
- if err != nil {
- js = []byte("error")
- }
-
- ws.SetWriteDeadline(time.Now().Add(writeWait))
- if err := ws.WriteMessage(websocket.TextMessage, js); err != nil {
- return
- }
- case <-pingTicker.C:
- ws.SetWriteDeadline(time.Now().Add(writeWait))
- if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
- return
- }
- }
- }
- }
-
- func serveWs(httpResult *model.HTTPResultsList) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- ws, err := upgrader.Upgrade(w, r, nil)
- if err != nil {
- if _, ok := err.(websocket.HandshakeError); !ok {
- log.Println(err)
- }
- return
- }
-
- go writer(ws, httpResult)
- reader(ws)
- }
- }
-
- func latestBeaconWriter(ws *websocket.Conn, latestBeaconsList map[string]model.Beacon, lock *sync.RWMutex) {
- pingTicker := time.NewTicker(pingPeriod)
- beaconTicker := time.NewTicker(beaconPeriod)
- defer func() {
- pingTicker.Stop()
- beaconTicker.Stop()
- ws.Close()
- }()
- for {
- select {
- case <-beaconTicker.C:
-
- lock.RLock()
- var la = make([]model.Beacon, 0)
- for _, b := range latestBeaconsList {
- la = append(la, b)
- }
- lock.RUnlock()
- js, err := json.Marshal(la)
-
- if err != nil {
- js = []byte("error")
- }
-
- ws.SetWriteDeadline(time.Now().Add(writeWait))
- if err := ws.WriteMessage(websocket.TextMessage, js); err != nil {
- return
- }
- case <-pingTicker.C:
- ws.SetWriteDeadline(time.Now().Add(writeWait))
- if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
- return
- }
- }
- }
- }
-
- func serveLatestBeaconsWs(latestList *model.LatestBeaconsList) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- ws, err := upgrader.Upgrade(w, r, nil)
- if err != nil {
- if _, ok := err.(websocket.HandshakeError); !ok {
- log.Println(err)
- }
- return
- }
-
- go latestBeaconWriter(ws, latestList.LatestList, &latestList.LatestListLock)
- reader(ws)
- }
- }
-
- func handleConnections(clients map[*websocket.Conn]bool, broadcast *chan model.Message) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- ws, err := upgrader.Upgrade(w, r, nil)
- if err != nil {
- log.Fatal(err)
- }
-
- defer ws.Close()
-
- clients[ws] = true
-
- for {
- var msg model.Message
- err := ws.ReadJSON(&msg)
- if err != nil {
- log.Printf("error: %v", err)
- delete(clients, ws)
- break
- }
- *broadcast <- msg
- }
- }
- }
-
- func handleMessages(clients map[*websocket.Conn]bool, broadcast *chan model.Message) {
- for {
- msg := <-*broadcast
- for client := range clients {
- err := client.WriteJSON(msg)
- if err != nil {
- log.Printf("error: %v", err)
- client.Close()
- delete(clients, client)
- }
- }
- }
- }
|