Http: split handlers into separate file

This commit is contained in:
andig 2021-10-10 17:12:52 +02:00
parent e6b396728d
commit ef4845a044
3 changed files with 300 additions and 320 deletions

View file

@ -1,20 +1,13 @@
package server
import (
"encoding/json"
"fmt"
"html/template"
"io/fs"
"net/http"
"os"
"strconv"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/test"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
@ -22,12 +15,6 @@ import (
// Assets is the embedded assets file system
var Assets fs.FS
const result = "result"
type errorJSON struct {
Error string `json:"error"`
}
type route struct {
Methods []string
Pattern string
@ -48,300 +35,6 @@ func routeLogger(inner http.Handler) http.HandlerFunc {
}
}
func indexHandler(site site.API) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
indexTemplate, err := fs.ReadFile(Assets, "index.html")
if err != nil {
log.FATAL.Print("httpd: failed to load embedded template:", err.Error())
log.FATAL.Fatal("Make sure templates are included using the `release` build tag or use `make build`")
}
t, err := template.New("evcc").Delims("[[", "]]").Parse(string(indexTemplate))
if err != nil {
log.FATAL.Fatal("httpd: failed to create main page template:", err.Error())
}
if err := t.Execute(w, map[string]interface{}{
"Version": Version,
"Commit": Commit,
"Configured": len(site.LoadPoints()),
}); err != nil {
log.ERROR.Println("httpd: failed to render main page:", err.Error())
}
})
}
// jsonHandler is a middleware that decorates responses with JSON and CORS headers
func jsonHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
h.ServeHTTP(w, r)
})
}
func jsonResponse(w http.ResponseWriter, r *http.Request, content interface{}) {
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(content); err != nil {
log.ERROR.Printf("httpd: failed to encode JSON: %v", err)
}
}
// HealthHandler returns current charge mode
func HealthHandler(site site.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !site.Healthy() {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "OK")
}
}
// TemplatesHandler returns current charge mode
func TemplatesHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
class, ok := vars["class"]
if !ok {
w.WriteHeader(http.StatusBadRequest)
return
}
type template = struct {
Name string `json:"name"`
Sample string `json:"template"`
}
res := make([]template, 0)
for _, conf := range test.ConfigTemplates(class) {
typedSample := fmt.Sprintf("type: %s\n%s", conf.Type, conf.Sample)
t := template{
Name: conf.Name,
Sample: typedSample,
}
res = append(res, t)
}
jsonResponse(w, r, res)
}
}
// StateHandler returns current charge mode
func StateHandler(cache *util.Cache) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
res := cache.State()
for _, k := range []string{"availableVersion", "releaseNotes"} {
delete(res, k)
}
jsonResponse(w, r, res)
}
}
// ChargeModeHandler updates charge mode
func ChargeModeHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
mode, err := api.ChargeModeString(vars["value"])
if err != nil {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, errorJSON{Error: err.Error()})
return
}
lp.SetMode(mode)
jsonResponse(w, r, map[string]interface{}{result: lp.GetMode()})
}
}
// TargetSoCHandler updates target soc
func TargetSoCHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
soc, err := strconv.ParseInt(vars["value"], 10, 32)
if err == nil {
err = lp.SetTargetSoC(int(soc))
}
if err != nil {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, errorJSON{Error: err.Error()})
return
}
jsonResponse(w, r, map[string]interface{}{result: lp.GetTargetSoC()})
}
}
// MinSoCHandler updates minimum soc
func MinSoCHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
soc, err := strconv.ParseInt(vars["value"], 10, 32)
if err == nil {
err = lp.SetMinSoC(int(soc))
}
if err != nil {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, errorJSON{Error: err.Error()})
return
}
jsonResponse(w, r, map[string]interface{}{result: lp.GetMinSoC()})
}
}
// MinCurrentHandler updates minimum current
func MinCurrentHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
current, err := strconv.ParseFloat(vars["value"], 64)
if err == nil {
lp.SetMinCurrent(current)
} else {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, errorJSON{Error: err.Error()})
return
}
jsonResponse(w, r, map[string]interface{}{result: lp.GetMinCurrent()})
}
}
// MaxCurrentHandler updates maximum current
func MaxCurrentHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
current, err := strconv.ParseFloat(vars["value"], 64)
if err == nil {
lp.SetMaxCurrent(current)
} else {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, errorJSON{Error: err.Error()})
return
}
jsonResponse(w, r, map[string]interface{}{result: lp.GetMaxCurrent()})
}
}
// PhasesHandler updates minimum soc
func PhasesHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
phases, err := strconv.ParseInt(vars["value"], 10, 32)
if err == nil {
err = lp.SetPhases(int(phases))
}
if err != nil {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, errorJSON{Error: err.Error()})
return
}
jsonResponse(w, r, map[string]interface{}{result: lp.GetPhases()})
}
}
// RemoteDemandHandler updates minimum soc
func RemoteDemandHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
demandS, ok := vars["demand"]
var source string
if ok {
source, ok = vars["source"]
}
demand, err := loadpoint.RemoteDemandString(demandS)
if !ok || err != nil {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, errorJSON{Error: err.Error()})
return
}
lp.RemoteControl(source, demand)
res := struct {
Demand loadpoint.RemoteDemand `json:"demand"`
Source string `json:"source"`
}{
Source: source,
Demand: demand,
}
jsonResponse(w, r, res)
}
}
func timezone() *time.Location {
tz := os.Getenv("TZ")
if tz == "" {
tz = "Local"
}
loc, _ := time.LoadLocation(tz)
return loc
}
// TargetChargeHandler updates target soc
func TargetChargeHandler(loadpoint loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
socS, ok := vars["soc"]
socV, err := strconv.ParseInt(socS, 10, 32)
if !ok || err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
timeS, ok := vars["time"]
timeV, err := time.ParseInLocation("2006-01-02T15:04:05", timeS, timezone())
if !ok || err != nil {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, errorJSON{Error: err.Error()})
return
}
loadpoint.SetTargetCharge(timeV, int(socV))
res := struct {
SoC int64 `json:"soc"`
Time time.Time `json:"time"`
}{
SoC: socV,
Time: timeV,
}
jsonResponse(w, r, res)
}
}
// SocketHandler attaches websocket handler to uri
func SocketHandler(hub *SocketHub) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ServeWebsocket(hub, w, r)
}
}
// HTTPd wraps an http.Server and adds the root router
type HTTPd struct {
*http.Server
@ -350,15 +43,14 @@ type HTTPd struct {
// NewHTTPd creates HTTP server with configured routes for loadpoint
func NewHTTPd(url string, site site.API, hub *SocketHub, cache *util.Cache) *HTTPd {
routes := map[string]route{
"health": {[]string{"GET"}, "/health", HealthHandler(site)},
"state": {[]string{"GET"}, "/state", StateHandler(cache)},
"templates": {[]string{"GET"}, "/config/templates/{class:[a-z]+}", TemplatesHandler()},
"health": {[]string{"GET"}, "/health", healthHandler(site)},
"state": {[]string{"GET"}, "/state", stateHandler(cache)},
}
router := mux.NewRouter().StrictSlash(true)
// websocket
router.HandleFunc("/ws", SocketHandler(hub))
router.HandleFunc("/ws", socketHandler(hub))
// static - individual handlers per root and folders
static := router.PathPrefix("/").Subrouter()
@ -389,14 +81,14 @@ func NewHTTPd(url string, site site.API, hub *SocketHub, cache *util.Cache) *HTT
lpAPI := api.PathPrefix(fmt.Sprintf("/loadpoints/%d", id)).Subrouter()
routes := map[string]route{
"mode": {[]string{"POST", "OPTIONS"}, "/mode/{value:[a-z]+}", ChargeModeHandler(lp)},
"targetsoc": {[]string{"POST", "OPTIONS"}, "/targetsoc/{value:[0-9]+}", TargetSoCHandler(lp)},
"minsoc": {[]string{"POST", "OPTIONS"}, "/minsoc/{value:[0-9]+}", MinSoCHandler(lp)},
"mincurrent": {[]string{"POST", "OPTIONS"}, "/mincurrent/{value:[0-9]+}", MinCurrentHandler(lp)},
"maxcurrent": {[]string{"POST", "OPTIONS"}, "/maxcurrent/{value:[0-9]+}", MaxCurrentHandler(lp)},
"phases": {[]string{"POST", "OPTIONS"}, "/phases/{value:[0-9]+}", PhasesHandler(lp)},
"targetcharge": {[]string{"POST", "OPTIONS"}, "/targetcharge/{soc:[0-9]+}/{time:[0-9TZ:-]+}", TargetChargeHandler(lp)},
"remotedemand": {[]string{"POST", "OPTIONS"}, "/remotedemand/{demand:[a-z]+}/{source::[0-9a-zA-Z_-]+}", RemoteDemandHandler(lp)},
"mode": {[]string{"POST", "OPTIONS"}, "/mode/{value:[a-z]+}", chargeModeHandler(lp)},
"targetsoc": {[]string{"POST", "OPTIONS"}, "/targetsoc/{value:[0-9]+}", targetSoCHandler(lp)},
"minsoc": {[]string{"POST", "OPTIONS"}, "/minsoc/{value:[0-9]+}", minSoCHandler(lp)},
"mincurrent": {[]string{"POST", "OPTIONS"}, "/mincurrent/{value:[0-9]+}", minCurrentHandler(lp)},
"maxcurrent": {[]string{"POST", "OPTIONS"}, "/maxcurrent/{value:[0-9]+}", maxCurrentHandler(lp)},
"phases": {[]string{"POST", "OPTIONS"}, "/phases/{value:[0-9]+}", phasesHandler(lp)},
"targetcharge": {[]string{"POST", "OPTIONS"}, "/targetcharge/{soc:[0-9]+}/{time:[0-9TZ:-]+}", targetChargeHandler(lp)},
"remotedemand": {[]string{"POST", "OPTIONS"}, "/remotedemand/{demand:[a-z]+}/{source::[0-9a-zA-Z_-]+}", remoteDemandHandler(lp)},
}
for _, r := range routes {

288
server/http_handler.go Normal file
View file

@ -0,0 +1,288 @@
package server
import (
"encoding/json"
"fmt"
"io/fs"
"net/http"
"os"
"strconv"
"text/template"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/core/site"
"github.com/evcc-io/evcc/util"
"github.com/gorilla/mux"
)
const (
result = "result"
failure = "error"
)
func indexHandler(site site.API) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
indexTemplate, err := fs.ReadFile(Assets, "index.html")
if err != nil {
log.FATAL.Print("httpd: failed to load embedded template:", err.Error())
log.FATAL.Fatal("Make sure templates are included using the `release` build tag or use `make build`")
}
t, err := template.New("evcc").Delims("[[", "]]").Parse(string(indexTemplate))
if err != nil {
log.FATAL.Fatal("httpd: failed to create main page template:", err.Error())
}
if err := t.Execute(w, map[string]interface{}{
"Version": Version,
"Commit": Commit,
"Configured": len(site.LoadPoints()),
}); err != nil {
log.ERROR.Println("httpd: failed to render main page:", err.Error())
}
})
}
// jsonHandler is a middleware that decorates responses with JSON and CORS headers
func jsonHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
h.ServeHTTP(w, r)
})
}
func jsonResponse(w http.ResponseWriter, r *http.Request, content interface{}) {
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(content); err != nil {
log.ERROR.Printf("httpd: failed to encode JSON: %v", err)
}
}
// healthHandler returns current charge mode
func healthHandler(site site.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !site.Healthy() {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "OK")
}
}
// stateHandler returns current charge mode
func stateHandler(cache *util.Cache) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
res := cache.State()
for _, k := range []string{"availableVersion", "releaseNotes"} {
delete(res, k)
}
jsonResponse(w, r, res)
}
}
// chargeModeHandler updates charge mode
func chargeModeHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
mode, err := api.ChargeModeString(vars["value"])
if err != nil {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, map[string]interface{}{failure: err.Error()})
return
}
lp.SetMode(mode)
jsonResponse(w, r, map[string]interface{}{result: lp.GetMode()})
}
}
// targetSoCHandler updates target soc
func targetSoCHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
soc, err := strconv.ParseInt(vars["value"], 10, 32)
if err == nil {
err = lp.SetTargetSoC(int(soc))
}
if err != nil {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, map[string]interface{}{failure: err.Error()})
return
}
jsonResponse(w, r, map[string]interface{}{result: lp.GetTargetSoC()})
}
}
// minSoCHandler updates minimum soc
func minSoCHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
soc, err := strconv.ParseInt(vars["value"], 10, 32)
if err == nil {
err = lp.SetMinSoC(int(soc))
}
if err != nil {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, map[string]interface{}{failure: err.Error()})
return
}
jsonResponse(w, r, map[string]interface{}{result: lp.GetMinSoC()})
}
}
// minCurrentHandler updates minimum current
func minCurrentHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
current, err := strconv.ParseFloat(vars["value"], 64)
if err == nil {
lp.SetMinCurrent(current)
} else {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, map[string]interface{}{failure: err.Error()})
return
}
jsonResponse(w, r, map[string]interface{}{result: lp.GetMinCurrent()})
}
}
// maxCurrentHandler updates maximum current
func maxCurrentHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
current, err := strconv.ParseFloat(vars["value"], 64)
if err == nil {
lp.SetMaxCurrent(current)
} else {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, map[string]interface{}{failure: err.Error()})
return
}
jsonResponse(w, r, map[string]interface{}{result: lp.GetMaxCurrent()})
}
}
// phasesHandler updates minimum soc
func phasesHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
phases, err := strconv.ParseInt(vars["value"], 10, 32)
if err == nil {
err = lp.SetPhases(int(phases))
}
if err != nil {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, map[string]interface{}{failure: err.Error()})
return
}
jsonResponse(w, r, map[string]interface{}{result: lp.GetPhases()})
}
}
// remoteDemandHandler updates minimum soc
func remoteDemandHandler(lp loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
demandS, ok := vars["demand"]
var source string
if ok {
source, ok = vars["source"]
}
demand, err := loadpoint.RemoteDemandString(demandS)
if !ok || err != nil {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, map[string]interface{}{failure: err.Error()})
return
}
lp.RemoteControl(source, demand)
res := struct {
Demand loadpoint.RemoteDemand `json:"demand"`
Source string `json:"source"`
}{
Source: source,
Demand: demand,
}
jsonResponse(w, r, res)
}
}
func timezone() *time.Location {
tz := os.Getenv("TZ")
if tz == "" {
tz = "Local"
}
loc, _ := time.LoadLocation(tz)
return loc
}
// targetChargeHandler updates target soc
func targetChargeHandler(loadpoint loadpoint.API) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
socS, ok := vars["soc"]
socV, err := strconv.ParseInt(socS, 10, 32)
if !ok || err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
timeS, ok := vars["time"]
timeV, err := time.ParseInLocation("2006-01-02T15:04:05", timeS, timezone())
if !ok || err != nil {
w.WriteHeader(http.StatusBadRequest)
jsonResponse(w, r, map[string]interface{}{failure: err.Error()})
return
}
loadpoint.SetTargetCharge(timeV, int(socV))
res := struct {
SoC int64 `json:"soc"`
Time time.Time `json:"time"`
}{
SoC: socV,
Time: timeV,
}
jsonResponse(w, r, res)
}
}
// socketHandler attaches websocket handler to uri
func socketHandler(hub *SocketHub) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ServeWebsocket(hub, w, r)
}
}

View file

@ -38,7 +38,7 @@ func HealthListener(site site.API) {
mux := http.NewServeMux()
httpd := http.Server{Handler: mux}
mux.HandleFunc("/health", HealthHandler(site))
mux.HandleFunc("/health", healthHandler(site))
_ = httpd.Serve(l)
}