package main import ( "encoding/json" "fmt" "log" "net" "strconv" "strings" "time" "net/http" "tplink/internal/tplink" "github.com/prometheus/client_golang/prometheus/promhttp" ) func httpAccessLog(req *http.Request) { log.Printf("[TRACE] %s - %s - %s\n", req.Method, req.RemoteAddr, req.RequestURI) } func crossSiteOrigin(w http.ResponseWriter) { w.Header().Add("Access-Control-Allow-Origin", "*") w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS") } func httpServer(host string, port int) { path := http.NewServeMux() connection := &http.Server{ Addr: host + ":" + strconv.FormatInt(int64(port), 10), Handler: path, ReadTimeout: time.Duration(config.WebSrvReadTimeout) * time.Second, WriteTimeout: time.Duration(config.WebSrvWriteTimeout) * time.Second, IdleTimeout: time.Duration(config.WebSrvIdleTimeout) * time.Second, } // metrics path.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) { httpAccessLog(r) crossSiteOrigin(w) promhttp.Handler().ServeHTTP(w, r) }) // api path.HandleFunc("/api/v1/tplink", webTPLink) // healthcheck path.HandleFunc("/healthcheck", webHealthCheck) // root path.HandleFunc("/", webRoot) if err := connection.ListenAndServe(); err != nil { log.Fatalf("[ERROR] %s\n", err) } } func webRoot(w http.ResponseWriter, r *http.Request) { httpAccessLog(r) crossSiteOrigin(w) if strings.ToLower(r.Method) == "get" { tmpltWebRoot(w) } else { log.Printf("[DEBUG] Request to '/' was made using the wrong method: expected %s, got %s\n", "GET", strings.ToUpper(r.Method)) tmpltError(w, http.StatusBadRequest, "Invalid http method.") } } func webHealthCheck(w http.ResponseWriter, r *http.Request) { httpAccessLog(r) crossSiteOrigin(w) if strings.ToLower(r.Method) == "get" { tmpltHealthCheck(w) } else { log.Printf("[DEBUG] Request to '/healthcheck' was made using the wrong method: expected %s, got %s\n", "GET", strings.ToUpper(r.Method)) tmpltError(w, http.StatusBadRequest, "Invalid http method.") } } func webTPLink(w http.ResponseWriter, r *http.Request) { httpAccessLog(r) crossSiteOrigin(w) if strings.ToLower(r.Method) == "get" { keys := r.URL.Query() if len(keys) == 0 { log.Printf("[INFO] Required parameters missing: no host, state, or deviceid was provided.\n") tmpltError(w, http.StatusBadRequest, "Required parameters missing: no parameter for host, state, or deviceid was provided.") return } var ( host string hostSet bool state bool stateSet bool id int ) for k, v := range keys { log.Printf("%s: %v\n", k, v) switch strings.ToLower(k) { case "host": if len(v) != 1 { log.Printf("[INFO] Host was defined multiple times.\n") tmpltError(w, http.StatusBadRequest, "Required parameter host was defined multiple times.") return } _, err := net.LookupHost(v[0]) if err != nil { log.Printf("[INFO] Unable to resolve provided hostname: %s\n", v[0]) tmpltError(w, http.StatusBadRequest, "Unable to resolve provided hostname.") return } hostSet = true host = v[0] case "state": if len(v) != 1 { log.Printf("[INFO] State was defined multiple times.\n") tmpltError(w, http.StatusBadRequest, "Required parameter state was defined multiple times.") return } s, err := strconv.ParseBool(v[0]) if err != nil { log.Printf("[INFO] State is not boolean: %s\n", v[0]) tmpltError(w, http.StatusBadRequest, "Required parameter state is not boolean.") return } stateSet = true state = s case "deviceid": if len(v) != 1 { log.Printf("[INFO] Deviceid was defined multiple times.\n") tmpltError(w, http.StatusBadRequest, "Required parameter device was defined multiple times.") return } d, err := strconv.Atoi(v[0]) if err != nil { log.Printf("[INFO] Deviceid is not an integer: %s\n", v[0]) tmpltError(w, http.StatusBadRequest, "Required parameter deviceid is not an integer.") return } id = d } } if !hostSet { log.Printf("[INFO] Required parameter missing: no host was provided.\n") tmpltError(w, http.StatusBadRequest, "Required parameters missing: no parameter for host provided.") return } if !stateSet { log.Printf("[INFO] Required parameter missing: no state was provided.\n") tmpltError(w, http.StatusBadRequest, "Required parameters missing: no parameter for state provided.") return } t := tplink.Tplink{ Host: host, SwitchID: id, } if err := t.ChangeStateMultiSwitch(state); err != nil { log.Printf("[INFO] Unable to change device state: %v\n", err) tmpltError(w, http.StatusInternalServerError, fmt.Sprintf("Unable to change device state: %v\n", err)) return } sysInfo, err := t.SystemInfo() if err != nil { log.Printf("[INFO] Unable to get info from target device: %v\n", err) tmpltError(w, http.StatusInternalServerError, fmt.Sprintf("Unable to read info from device: %v\n", err)) return } var ds int if len(sysInfo.System.GetSysinfo.Children) == 0 { ds = sysInfo.System.GetSysinfo.RelayState } else { ds = sysInfo.System.GetSysinfo.Children[id].State } var fs string if ds == 0 { fs = "OFF" } else { fs = "ON" } o := struct { Status string `json:"status" yaml:"status"` Host string `json:"host" yaml:"host"` DeviceID int `json:"device_id" yaml:"deviceID"` }{ Status: fs, Host: host, DeviceID: id, } w.Header().Add("Content-Type", "application/json") output, err := json.MarshalIndent(o, "", " ") if err != nil { log.Printf("[TRACE] Unable to marshal error message: %v.", err) } w.Write(output) //nolint:errcheck } else if strings.ToLower(r.Method) == "options" { return } else { log.Printf("[DEBUG] Request to '%s' was made using the wrong method: expected %s, got %s", "GET|OPTIONS", r.URL.Path, strings.ToUpper(r.Method)) tmpltError(w, http.StatusBadRequest, "Invalid http method.") } }