feat(relay): accept a full URL host so relays work behind an HTTPS proxy
Network relay boards (WebSwitch/KMTronic/Dingtian) were always reached at http://<host>, so a board on the LAN behind a reverse proxy — the common remote setup where the operator's 80/443 already go to Nginx Proxy Manager — couldn't be reached from outside. relayBase() now keeps a scheme the operator supplies (https://relay.example.com, optional sub-path) and only defaults to http:// for a bare host/host:port. UI hint + test added.
This commit is contained in:
@@ -61,6 +61,22 @@ func get(ctx context.Context, url, user, pass string) ([]byte, error) {
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// relayBase turns a configured host into the base URL of a network relay board.
|
||||
// A bare host or host:port keeps the default http:// (the boards serve plain
|
||||
// HTTP on their LAN). A host that already carries a scheme is used verbatim, so
|
||||
// a board can be reached through an HTTPS reverse proxy — e.g. a Nginx Proxy
|
||||
// Manager entry "https://relay.example.com" fronting the LAN board's port 80,
|
||||
// which is how an operator reaches shack relays from outside when 80/443 are
|
||||
// already taken by the proxy. A trailing slash is trimmed so path parts append
|
||||
// cleanly; an optional sub-path in the host is preserved (a sub-path proxy).
|
||||
func relayBase(host string) string {
|
||||
h := strings.TrimSpace(host)
|
||||
if strings.HasPrefix(h, "http://") || strings.HasPrefix(h, "https://") {
|
||||
return strings.TrimRight(h, "/")
|
||||
}
|
||||
return "http://" + h
|
||||
}
|
||||
|
||||
// ── WebSwitch 1216H ────────────────────────────────────────────────────
|
||||
|
||||
type webswitch struct {
|
||||
@@ -82,7 +98,7 @@ func (w *webswitch) Set(ctx context.Context, relay int, on bool) error {
|
||||
if on {
|
||||
action = "on"
|
||||
}
|
||||
_, err := get(ctx, fmt.Sprintf("http://%s/relaycontrol/%s/%d", w.host, action, relay), "", "")
|
||||
_, err := get(ctx, fmt.Sprintf("%s/relaycontrol/%s/%d", relayBase(w.host), action, relay), "", "")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -93,7 +109,7 @@ func (w *webswitch) Status(ctx context.Context) ([]bool, error) {
|
||||
sel.WriteString(strconv.Itoa(i))
|
||||
sel.WriteByte('$')
|
||||
}
|
||||
body, err := get(ctx, fmt.Sprintf("http://%s/relaystate/get2/%s", w.host, sel.String()), "", "")
|
||||
body, err := get(ctx, fmt.Sprintf("%s/relaystate/get2/%s", relayBase(w.host), sel.String()), "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -140,7 +156,7 @@ func (k *kmtronic) Set(ctx context.Context, relay int, on bool) error {
|
||||
state = "01"
|
||||
}
|
||||
// FF<rr><ss>: e.g. FF0101 = relay 1 on, FF0800 = relay 8 off.
|
||||
_, err := get(ctx, fmt.Sprintf("http://%s/FF%02d%s", k.host, relay, state), k.user, k.pass)
|
||||
_, err := get(ctx, fmt.Sprintf("%s/FF%02d%s", relayBase(k.host), relay, state), k.user, k.pass)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -154,7 +170,7 @@ type kmStatus struct {
|
||||
}
|
||||
|
||||
func (k *kmtronic) Status(ctx context.Context) ([]bool, error) {
|
||||
body, err := get(ctx, fmt.Sprintf("http://%s/status.xml", k.host), k.user, k.pass)
|
||||
body, err := get(ctx, fmt.Sprintf("%s/status.xml", relayBase(k.host)), k.user, k.pass)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -259,8 +275,8 @@ func (d *dingtian) Set(ctx context.Context, relay int, on bool) error {
|
||||
}
|
||||
// type=0 is plain ON/OFF (1 = jogging, 2 = delay, 3 = flash, 4 = toggle),
|
||||
// and time is then unused.
|
||||
body, err := d.getCGI(ctx, fmt.Sprintf("http://%s/relay_cgi.cgi?type=0&relay=%d&on=%d&time=0&pwd=%s&",
|
||||
d.host, relay-1, state, d.pwd))
|
||||
body, err := d.getCGI(ctx, fmt.Sprintf("%s/relay_cgi.cgi?type=0&relay=%d&on=%d&time=0&pwd=%s&",
|
||||
relayBase(d.host), relay-1, state, d.pwd))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -274,7 +290,7 @@ func (d *dingtian) Set(ctx context.Context, relay int, on bool) error {
|
||||
}
|
||||
|
||||
func (d *dingtian) Status(ctx context.Context) ([]bool, error) {
|
||||
body, err := d.getCGI(ctx, fmt.Sprintf("http://%s/relay_cgi_load.cgi", d.host))
|
||||
body, err := d.getCGI(ctx, fmt.Sprintf("%s/relay_cgi_load.cgi", relayBase(d.host)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -172,3 +172,19 @@ func TestDingtianSetRefusalIsAnError(t *testing.T) {
|
||||
t.Fatal("a refused command answered HTTP 200 and was reported as success")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRelayBase(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"192.168.1.5": "http://192.168.1.5",
|
||||
"192.168.1.5:8080": "http://192.168.1.5:8080",
|
||||
"https://relay.example.com": "https://relay.example.com",
|
||||
"https://relay.example.com/": "https://relay.example.com",
|
||||
"http://relay.example.com/sw/": "http://relay.example.com/sw",
|
||||
" relay.local ": "http://relay.local",
|
||||
}
|
||||
for in, want := range cases {
|
||||
if got := relayBase(in); got != want {
|
||||
t.Errorf("relayBase(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user