183 lines
4.5 KiB
Go
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
|
|
}
|