srs/internal/bootstrap/proxy.go
Winlin 3663a8e38f
Proxy: Refactor server APIs and expand RTMP test coverage. v7.0.147 (#4672)
This PR refactors the Go proxy server internals and significantly
expands RTMP/proxy verification coverage.

- Rename internal/protocol to internal/server to better describe the
package responsibility.
- Refactor proxy server constructors and types toward cleaner exported
interfaces:
      - NewRTMPServer
      - NewWebRTCServer
      - NewHTTPAPIServer
      - NewHTTPStreamServer
      - NewSystemAPI
  - Expose RTMP protocol interfaces for better testability:
      - Handshake
      - Protocol
      - Message
- AMF0 public interfaces such as Amf0Any, Amf0Number, Amf0String,
Amf0Object, etc.
- Add RTMP unit tests covering AMF0, handshake, protocol messages,
packet encoding/decoding, and API examples.
  - Add generated RTMP fakes for interface-based tests.
  - Add proxy E2E scripts for:
      - multi-origin memory load-balancer routing
      - Redis multi-proxy routing
- RTMP transmuxing verification across RTMP, HTTP-FLV, HLS, and optional
WebRTC WHEP
- Update OpenClaw/SRSBot development docs and memory to reflect the new
package layout, new verification scripts, and unsupported origin/edge
development scope.

---------

Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
2026-05-02 09:36:55 -04:00

148 lines
4.4 KiB
Go

// Copyright (c) 2026 Winlin
//
// SPDX-License-Identifier: MIT
package bootstrap
import (
"context"
"time"
"srsx/internal/debug"
"srsx/internal/env"
"srsx/internal/errors"
"srsx/internal/lb"
"srsx/internal/logger"
"srsx/internal/server"
"srsx/internal/signal"
"srsx/internal/version"
)
// NewProxyBootstrap creates a new Bootstrap instance for the proxy server.
func NewProxyBootstrap() Bootstrap {
return &proxyBootstrap{}
}
// proxyBootstrap implements the Bootstrap interface for the proxy server.
type proxyBootstrap struct{}
// Start initializes the context with logger and signal handlers, then runs the bootstrap.
// Returns any error encountered during startup.
func (b *proxyBootstrap) Start(ctx context.Context) error {
ctx = logger.WithContext(ctx)
logger.Debug(ctx, "%v-Proxy/%v started", version.Signature(), version.Version())
// Install signals.
ctx, cancel := context.WithCancel(ctx)
signal.InstallSignals(ctx, cancel)
// Run the main loop, ignore the user cancel error.
err := b.run(ctx)
if err != nil && ctx.Err() != context.Canceled {
logger.Error(ctx, "main: %+v", err)
return err
}
logger.Debug(ctx, "%v done", version.Signature())
return nil
}
// Run initializes and starts all proxy servers and the load balancer.
// It blocks until the context is cancelled.
func (b *proxyBootstrap) run(ctx context.Context) error {
// Setup the environment variables.
environment, err := env.NewProxyEnvironment(ctx)
if err != nil {
return errors.Wrapf(err, "create environment")
}
// When cancelled, the program is forced to exit due to a timeout. Normally, this doesn't occur
// because the main thread exits after the context is cancelled. However, sometimes the main thread
// may be blocked for some reason, so a forced exit is necessary to ensure the program terminates.
if err := signal.InstallForceQuit(ctx, environment); err != nil {
return errors.Wrapf(err, "install force quit")
}
// Start the Go pprof if enabled.
debug.HandleGoPprof(ctx, environment)
// Initialize the load balancer.
if err := b.initializeLoadBalancer(ctx, environment); err != nil {
return err
}
// Parse the gracefully quit timeout.
gracefulQuitTimeout, err := time.ParseDuration(environment.GraceQuitTimeout())
if err != nil {
return errors.Wrapf(err, "parse gracefully quit timeout")
}
// Start all servers and block until context is cancelled.
return b.startServers(ctx, environment, gracefulQuitTimeout)
}
// initializeLoadBalancer sets up the load balancer based on configuration.
func (b *proxyBootstrap) initializeLoadBalancer(ctx context.Context, environment env.ProxyEnvironment) error {
switch environment.LoadBalancerType() {
case "redis":
lb.SrsLoadBalancer = lb.NewRedisLoadBalancer(environment)
default:
lb.SrsLoadBalancer = lb.NewMemoryLoadBalancer(environment)
}
if err := lb.SrsLoadBalancer.Initialize(ctx); err != nil {
return errors.Wrapf(err, "initialize srs load balancer")
}
return nil
}
// startServers initializes and starts all protocol servers.
func (b *proxyBootstrap) startServers(ctx context.Context, environment env.ProxyEnvironment, gracefulQuitTimeout time.Duration) error {
// Start the RTMP server.
rtmpServer := server.NewRTMPServer(environment)
if err := rtmpServer.Run(ctx); err != nil {
return errors.Wrapf(err, "rtmp server")
}
defer rtmpServer.Close()
// Start the WebRTC server.
webRTCServer := server.NewWebRTCServer(environment)
if err := webRTCServer.Run(ctx); err != nil {
return errors.Wrapf(err, "rtc server")
}
defer webRTCServer.Close()
// Start the HTTP API server.
httpAPIServer := server.NewHTTPAPIServer(environment, gracefulQuitTimeout, webRTCServer)
if err := httpAPIServer.Run(ctx); err != nil {
return errors.Wrapf(err, "http api server")
}
defer httpAPIServer.Close()
// Start the SRT server.
srsSRTServer := server.NewSRSSRTServer(environment)
if err := srsSRTServer.Run(ctx); err != nil {
return errors.Wrapf(err, "srt server")
}
defer srsSRTServer.Close()
// Start the System API server.
systemAPI := server.NewSystemAPI(environment, gracefulQuitTimeout)
if err := systemAPI.Run(ctx); err != nil {
return errors.Wrapf(err, "system api server")
}
defer systemAPI.Close()
// Start the HTTP web server.
httpStreamServer := server.NewHTTPStreamServer(environment, gracefulQuitTimeout)
if err := httpStreamServer.Run(ctx); err != nil {
return errors.Wrapf(err, "http server")
}
defer httpStreamServer.Close()
// Wait for the main loop to quit.
<-ctx.Done()
return nil
}