Files
rdp-proxy/agents/rap-node-agent/internal/webingress/manager.go
T

183 lines
4.5 KiB
Go

package webingress
import (
"context"
"crypto/tls"
"errors"
"net"
"net/http"
"strings"
"sync"
"time"
)
type ListenerConfig struct {
RuntimeConfig
HTTPAddr string
HTTPSAddr string
TLSCertFile string
TLSKeyFile string
Binder FabricBinder
}
type ListenerStatus struct {
SchemaVersion string `json:"schema_version"`
Running bool `json:"running"`
HTTPRunning bool `json:"http_running"`
HTTPSRunning bool `json:"https_running"`
HTTPAddr string `json:"http_addr,omitempty"`
HTTPSAddr string `json:"https_addr,omitempty"`
Reason string `json:"reason,omitempty"`
Errors []string `json:"errors,omitempty"`
ObservedAt string `json:"observed_at"`
}
type Manager struct {
mu sync.Mutex
http *http.Server
https *http.Server
status ListenerStatus
now func() time.Time
}
func NewManager() *Manager {
return &Manager{now: time.Now}
}
func (m *Manager) Apply(ctx context.Context, cfg ListenerConfig) ListenerStatus {
m.mu.Lock()
defer m.mu.Unlock()
_ = m.stopLocked(ctx)
runtime := Runtime{Config: cfg.RuntimeConfig, Binder: cfg.Binder, Now: m.now}
status := ListenerStatus{
SchemaVersion: "rap.web_ingress.listener_status.v1",
Reason: "started",
ObservedAt: m.observedAt(),
}
errorsOut := []string{}
if strings.TrimSpace(cfg.HTTPAddr) == "" {
cfg.HTTPAddr = ":80"
}
if strings.TrimSpace(cfg.HTTPSAddr) == "" {
cfg.HTTPSAddr = ":443"
}
if server, addr, err := startHTTPServer(ctx, cfg.HTTPAddr, runtime.HTTPHandler()); err == nil {
m.http = server
status.HTTPRunning = true
status.HTTPAddr = addr
} else {
errorsOut = append(errorsOut, "http:"+err.Error())
}
if cfg.TLSCertFile == "" || cfg.TLSKeyFile == "" {
errorsOut = append(errorsOut, "https:tls_cert_file_and_key_file_required")
} else if server, addr, err := startHTTPSServer(ctx, cfg.HTTPSAddr, cfg.TLSCertFile, cfg.TLSKeyFile, runtime.HTTPSHandler()); err == nil {
m.https = server
status.HTTPSRunning = true
status.HTTPSAddr = addr
} else {
errorsOut = append(errorsOut, "https:"+err.Error())
}
status.Running = status.HTTPRunning || status.HTTPSRunning
if len(errorsOut) > 0 {
status.Errors = errorsOut
if status.Running {
status.Reason = "partial"
} else {
status.Reason = "blocked"
}
}
m.status = status
return status
}
func (m *Manager) Stop(ctx context.Context) ListenerStatus {
m.mu.Lock()
defer m.mu.Unlock()
_ = m.stopLocked(ctx)
m.status = ListenerStatus{
SchemaVersion: "rap.web_ingress.listener_status.v1",
Reason: "stopped",
ObservedAt: m.observedAt(),
}
return m.status
}
func (m *Manager) Status() ListenerStatus {
m.mu.Lock()
defer m.mu.Unlock()
if m.status.SchemaVersion == "" {
return ListenerStatus{
SchemaVersion: "rap.web_ingress.listener_status.v1",
Reason: "not_started",
ObservedAt: m.observedAt(),
}
}
return m.status
}
func (m *Manager) stopLocked(ctx context.Context) error {
var out error
if m.http != nil {
out = errors.Join(out, m.http.Shutdown(ctx))
m.http = nil
}
if m.https != nil {
out = errors.Join(out, m.https.Shutdown(ctx))
m.https = nil
}
return out
}
func (m *Manager) observedAt() string {
now := time.Now().UTC()
if m.now != nil {
now = m.now().UTC()
}
return now.Format(time.RFC3339Nano)
}
func startHTTPServer(ctx context.Context, addr string, handler http.Handler) (*http.Server, string, error) {
listener, err := net.Listen("tcp", addr)
if err != nil {
return nil, "", err
}
server := &http.Server{Handler: handler, ReadHeaderTimeout: 5 * time.Second}
go func() {
<-ctx.Done()
_ = server.Shutdown(context.Background())
}()
go func() {
if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
_ = server.Close()
}
}()
return server, listener.Addr().String(), nil
}
func startHTTPSServer(ctx context.Context, addr, certFile, keyFile string, handler http.Handler) (*http.Server, string, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, "", err
}
listener, err := net.Listen("tcp", addr)
if err != nil {
return nil, "", err
}
server := &http.Server{
Handler: handler,
ReadHeaderTimeout: 5 * time.Second,
TLSConfig: &tls.Config{MinVersion: tls.VersionTLS12, Certificates: []tls.Certificate{cert}},
}
go func() {
<-ctx.Done()
_ = server.Shutdown(context.Background())
}()
go func() {
if err := server.ServeTLS(listener, "", ""); err != nil && !errors.Is(err, http.ErrServerClosed) {
_ = server.Close()
}
}()
return server, listener.Addr().String(), nil
}