chore: refactor plugin structure (#18335)

This commit is contained in:
andig 2025-01-23 14:43:04 +01:00 • committed by GitHub
parent 9127e1a913
commit 07fa1b64f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
127 changed files with 861 additions and 938 deletions

164
plugin/socket.go Normal file
View file

@ -0,0 +1,164 @@
package plugin
import (
"context"
"net/http"
"sync"
"time"
"github.com/coder/websocket"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/plugin/pipeline"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/util/transport"
)
const retryDelay = 5 * time.Second
// Socket implements websocket request provider
type Socket struct {
*getter
*request.Helper
log *util.Logger
url string
headers map[string]string
pipeline *pipeline.Pipeline
val *util.Monitor[[]byte]
}
func init() {
registry.Add("ws", NewSocketPluginFromConfig)
registry.Add("websocket", NewSocketPluginFromConfig)
}
// NewSocketPluginFromConfig creates a HTTP provider
func NewSocketPluginFromConfig(other map[string]interface{}) (Plugin, error) {
cc := struct {
URI string
Headers map[string]string
pipeline.Settings `mapstructure:",squash"`
Scale float64
Insecure bool
Auth Auth
Timeout time.Duration
}{
Headers: make(map[string]string),
Scale: 1,
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
log := util.NewLogger("ws")
url := util.DefaultScheme(cc.URI, "ws")
if url != cc.URI {
log.WARN.Printf("missing scheme for %s, assuming ws", cc.URI)
}
p := &Socket{
log: log,
Helper: request.NewHelper(log),
url: url,
headers: cc.Headers,
val: util.NewMonitor[[]byte](cc.Timeout),
}
p.getter = defaultGetters(p, cc.Scale)
// handle basic auth
if cc.Auth.Type != "" {
basicAuth := transport.BasicAuthHeader(cc.Auth.User, cc.Auth.Password)
log.Redact(basicAuth)
p.headers["Authorization"] = basicAuth
}
// ignore the self signed certificate
if cc.Insecure {
p.Client.Transport = request.NewTripper(log, transport.Insecure())
}
var err error
if p.pipeline, err = pipeline.New(log, cc.Settings); err != nil {
return nil, err
}
errC := make(chan error, 1)
go p.run(errC)
if cc.Timeout > 0 {
select {
case <-p.val.Done():
case <-time.After(cc.Timeout):
return nil, api.ErrTimeout
case err := <-errC:
return nil, err
}
}
return p, nil
}
func (p *Socket) run(errC chan error) {
var once sync.Once
headers := make(http.Header)
for k, v := range p.headers {
headers.Set(k, v)
}
opts := &websocket.DialOptions{
HTTPHeader: headers,
}
for {
ctx, cancel := context.WithTimeout(context.Background(), request.Timeout)
conn, _, err := websocket.Dial(ctx, p.url, opts)
cancel()
if err != nil {
// handle initial connection error immediately
once.Do(func() { errC <- err })
p.log.ERROR.Println(err)
time.Sleep(retryDelay)
continue
}
for {
_, b, err := conn.Read(context.Background())
if err != nil {
p.log.TRACE.Println("read:", err)
_ = conn.Close(websocket.StatusAbnormalClosure, "done")
break
}
p.log.TRACE.Printf("recv: %s", b)
if v, err := p.pipeline.Process(b); err == nil {
p.val.Set(v)
}
}
}
}
var _ Getters = (*Socket)(nil)
// StringGetter sends string request
func (p *Socket) StringGetter() (func() (string, error), error) {
return func() (string, error) {
val, err := p.val.Get()
if err != nil {
return "", err
}
if err := knownErrors(val); err != nil {
return "", err
}
return string(val), nil
}, nil
}