From 62052e0159ba7f89310ce3a779eb867bbd11c280 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 10 May 2021 21:58:04 +0200 Subject: [PATCH] Fix memory leaks due to not closing http request body (#997) --- internal/charger/evsewifi.go | 9 +++++---- internal/vehicle/porsche/identity.go | 7 +------ soc/server/ui/ui.go | 10 ++++++++++ util/request/functions.go | 14 -------------- util/request/helper.go | 20 ++++++++++++++++---- 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/internal/charger/evsewifi.go b/internal/charger/evsewifi.go index a1eb3f27c..d370760f0 100644 --- a/internal/charger/evsewifi.go +++ b/internal/charger/evsewifi.go @@ -181,8 +181,9 @@ func (evse *EVSEWifi) Enabled() (bool, error) { return params.EvseState, err } -// checkError checks for EVSE error response with HTTP 200 status -func (evse *EVSEWifi) checkError(b []byte, err error) error { +// get executes GET request and checks for EVSE error response +func (evse *EVSEWifi) get(uri string) error { + b, err := evse.GetBody(uri) if err == nil && !strings.HasPrefix(string(b), evseSuccess) { err = errors.New(string(b)) } @@ -200,14 +201,14 @@ func (evse *EVSEWifi) Enable(enable bool) error { } url = fmt.Sprintf("%s?current=%d", evse.apiURL(evseSetCurrent), current) } - return evse.checkError(evse.GetBody(url)) + return evse.get(url) } // MaxCurrent implements the api.Charger interface func (evse *EVSEWifi) MaxCurrent(current int64) error { evse.current = current url := fmt.Sprintf("%s?current=%d", evse.apiURL(evseSetCurrent), current) - return evse.checkError(evse.GetBody(url)) + return evse.get(url) } var _ api.ChargeTimer = (*EVSEWifi)(nil) diff --git a/internal/vehicle/porsche/identity.go b/internal/vehicle/porsche/identity.go index 1cc9f9bf4..f7eb1bc0a 100644 --- a/internal/vehicle/porsche/identity.go +++ b/internal/vehicle/porsche/identity.go @@ -180,12 +180,8 @@ func (v *Identity) fetchToken(emobility bool) (tokenResponse, error) { } req, err = request.New(http.MethodPost, "https://login.porsche.com/as/token.oauth2", strings.NewReader(dataAPIToken.Encode()), request.URLEncoding) - if err == nil { - resp, err = v.Client.Do(req) - if err == nil { - err = request.DecodeJSON(resp, &pr) - } + err = v.DoJSON(req, &pr) } if pr.AccessToken == "" || pr.ExpiresIn == 0 { @@ -212,7 +208,6 @@ func (v *Identity) FindVehicle(accessTokens AccessTokens, vin string) (Vehicle, }) var vehicles []VehicleResponse - if err == nil { err = v.DoJSON(req, &vehicles) } diff --git a/soc/server/ui/ui.go b/soc/server/ui/ui.go index 1a2c771d5..87d840f03 100644 --- a/soc/server/ui/ui.go +++ b/soc/server/ui/ui.go @@ -10,6 +10,7 @@ import ( "log" "math/rand" "net/http" + "net/http/pprof" "os" "strings" "time" @@ -17,6 +18,7 @@ import ( "github.com/andig/evcc/soc/server/auth" "github.com/andig/evcc/util" "github.com/dgrijalva/jwt-go" + "github.com/prometheus/client_golang/prometheus/promhttp" "golang.org/x/crypto/acme/autocert" "golang.org/x/oauth2" "golang.org/x/oauth2/github" @@ -214,6 +216,14 @@ func Run() { mux.HandleFunc("/login", handleLogin) mux.HandleFunc("/callback", handleCallback) + mux.Handle("/metrics", promhttp.Handler()) + + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + // mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + // mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + // mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + s := &http.Server{ Addr: ":http", Handler: mux, diff --git a/util/request/functions.go b/util/request/functions.go index a0abdf2fb..c30c85d28 100644 --- a/util/request/functions.go +++ b/util/request/functions.go @@ -1,8 +1,6 @@ package request import ( - "bytes" - "encoding/json" "fmt" "io" "net/http" @@ -67,9 +65,6 @@ func ReadBody(resp *http.Response) ([]byte, error) { return []byte{}, err } - // maintain body after reading - resp.Body = io.NopCloser(bytes.NewBuffer(b)) - if resp.StatusCode < 200 || resp.StatusCode >= 300 { return b, StatusError{resp: resp} } @@ -77,15 +72,6 @@ func ReadBody(resp *http.Response) ([]byte, error) { return b, nil } -// DecodeJSON reads HTTP response and decodes JSON body if error is nil -func DecodeJSON(resp *http.Response, res interface{}) error { - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return StatusError{resp: resp} - } - - return json.NewDecoder(resp.Body).Decode(&res) -} - // New builds and executes HTTP request and returns the response func New(method, uri string, data io.Reader, headers ...map[string]string) (*http.Request, error) { req, err := http.NewRequest(method, uri, data) diff --git a/util/request/helper.go b/util/request/helper.go index 23ad42fbd..aca42b26d 100644 --- a/util/request/helper.go +++ b/util/request/helper.go @@ -1,6 +1,7 @@ package request import ( + "encoding/json" "net/http" "time" @@ -47,20 +48,31 @@ func (r *Helper) GetBody(url string) ([]byte, error) { return body, err } -// DoJSON executes HTTP request and decodes JSON response +// decodeJSON reads HTTP response and decodes JSON body if error is nil +func decodeJSON(resp *http.Response, res interface{}) error { + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return StatusError{resp: resp} + } + + return json.NewDecoder(resp.Body).Decode(&res) +} + +// DoJSON executes HTTP request and decodes JSON response. It returns a StatusError on response codes other than HTTP 2xx. func (r *Helper) DoJSON(req *http.Request, res interface{}) error { resp, err := r.Do(req) if err == nil { - err = DecodeJSON(resp, &res) + defer resp.Body.Close() + err = decodeJSON(resp, &res) } return err } -// GetJSON executes HTTP GET request and decodes JSON response +// GetJSON executes HTTP GET request and decodes JSON response. It returns a StatusError on response codes other than HTTP 2xx. func (r *Helper) GetJSON(url string, res interface{}) error { resp, err := r.Get(url) if err == nil { - err = DecodeJSON(resp, &res) + defer resp.Body.Close() + err = decodeJSON(resp, &res) } return err }