Skip to content

Commit 36a9f86

Browse files
ossrs-aiwinlinvip
authored andcommitted
AI: Refine bootstrap and env to interface.
1 parent d508af9 commit 36a9f86

File tree

17 files changed

+415
-430
lines changed

17 files changed

+415
-430
lines changed

CLAUDE.md

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,38 +11,6 @@ Key characteristics:
1111
- Backend origin servers register via System API
1212
- Official solution for SRS Origin Cluster
1313

14-
## Design Overview
15-
16-
See @doc/design.md for the complete architecture overview, including:
17-
- Stateless proxy architecture with built-in load balancing
18-
- Single Proxy Mode (memory-based)
19-
- Multi-Proxy Mode (Redis sync, AWS NLB)
20-
- Complete Cluster (Edge + Proxy + Origins)
21-
22-
## Configuration
23-
24-
All configuration via environment variables (`.env` file supported):
25-
26-
### Server Listen Ports (client-facing)
27-
- `PROXY_RTMP_SERVER=11935` - RTMP media server
28-
- `PROXY_HTTP_SERVER=18080` - HTTP streaming (HLS, HTTP-FLV)
29-
- `PROXY_WEBRTC_SERVER=18000` - WebRTC server (UDP)
30-
- `PROXY_SRT_SERVER=20080` - SRT server (UDP)
31-
- `PROXY_HTTP_API=11985` - HTTP API (WHIP/WHEP)
32-
- `PROXY_SYSTEM_API=12025` - System API (origin registration)
33-
34-
### Load Balancer Configuration
35-
- `PROXY_LOAD_BALANCER_TYPE=memory` - Use "memory" (single proxy) or "redis" (multi-proxy)
36-
- `PROXY_REDIS_HOST=127.0.0.1`
37-
- `PROXY_REDIS_PORT=6379`
38-
- `PROXY_REDIS_PASSWORD=` (empty for no password)
39-
- `PROXY_REDIS_DB=0`
40-
41-
### Other Settings
42-
- `PROXY_STATIC_FILES=../srs/trunk/research` - Static web files directory
43-
- `PROXY_FORCE_QUIT_TIMEOUT=30s` - Force shutdown timeout
44-
- `PROXY_GRACE_QUIT_TIMEOUT=20s` - Graceful shutdown timeout
45-
4614
## How to Run
4715

4816
When running the project for testing or development, you should:
@@ -85,3 +53,22 @@ ffprobe http://localhost:8080/live/livestream.flv
8553
```
8654

8755
Both commands should successfully detect the stream and display video/audio codec information. If ffprobe shows stream details without errors, the proxy is working correctly.
56+
57+
## Code Conventions
58+
59+
### Factory Functions
60+
- Factory functions should use explicit interface names: `NewBootstrap()`, `NewMemoryLoadBalancer()`, etc.
61+
- **Do not** use generic `New()` function names
62+
- This improves code clarity and makes the constructed type explicit at the call site
63+
- Example:
64+
```go
65+
// Good
66+
bs := bootstrap.NewBootstrap()
67+
68+
// Avoid
69+
bs := bootstrap.New()
70+
```
71+
72+
### Global Variables
73+
- Avoid global variables for service instances
74+
- This improves testability and makes code flow explicit

cmd/proxy-go/main.go

Lines changed: 3 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -7,134 +7,12 @@ import (
77
"context"
88
"os"
99

10-
"srs-proxy/internal/debug"
11-
"srs-proxy/internal/env"
12-
"srs-proxy/internal/errors"
13-
"srs-proxy/internal/lb"
14-
"srs-proxy/internal/logger"
15-
"srs-proxy/internal/protocol"
16-
"srs-proxy/internal/signal"
17-
"srs-proxy/internal/utils"
18-
"srs-proxy/internal/version"
10+
"srs-proxy/internal/bootstrap"
1911
)
2012

2113
func main() {
22-
ctx := logger.WithContext(context.Background())
23-
logger.Df(ctx, "%v/%v started", version.Signature(), version.Version())
24-
25-
// Install signals.
26-
ctx, cancel := context.WithCancel(ctx)
27-
signal.InstallSignals(ctx, cancel)
28-
29-
// Start the main loop, ignore the user cancel error.
30-
err := doMain(ctx)
31-
if err != nil && ctx.Err() != context.Canceled {
32-
logger.Ef(ctx, "main: %+v", err)
14+
bs := bootstrap.NewBootstrap()
15+
if err := bs.Start(context.Background()); err != nil {
3316
os.Exit(-1)
3417
}
35-
36-
logger.Df(ctx, "%v done", version.Signature())
37-
}
38-
39-
func doMain(ctx context.Context) error {
40-
// Setup the environment variables.
41-
if err := env.LoadEnvFile(ctx); err != nil {
42-
return errors.Wrapf(err, "load env")
43-
}
44-
45-
env.BuildDefaultEnvironmentVariables(ctx)
46-
47-
// When cancelled, the program is forced to exit due to a timeout. Normally, this doesn't occur
48-
// because the main thread exits after the context is cancelled. However, sometimes the main thread
49-
// may be blocked for some reason, so a forced exit is necessary to ensure the program terminates.
50-
if err := signal.InstallForceQuit(ctx); err != nil {
51-
return errors.Wrapf(err, "install force quit")
52-
}
53-
54-
// Start the Go pprof if enabled.
55-
debug.HandleGoPprof(ctx)
56-
57-
// Initialize the load balancer.
58-
switch env.EnvLoadBalancerType() {
59-
case "redis":
60-
lb.SrsLoadBalancer = lb.NewRedisLoadBalancer(
61-
env.EnvRedisHost,
62-
env.EnvRedisPort,
63-
env.EnvRedisPassword,
64-
env.EnvRedisDB,
65-
env.EnvDefaultBackendEnabled,
66-
env.EnvDefaultBackendIP,
67-
env.EnvDefaultBackendRTMP,
68-
env.EnvDefaultBackendHttp,
69-
env.EnvDefaultBackendAPI,
70-
env.EnvDefaultBackendRTC,
71-
env.EnvDefaultBackendSRT,
72-
)
73-
default:
74-
lb.SrsLoadBalancer = lb.NewMemoryLoadBalancer(
75-
env.EnvDefaultBackendEnabled,
76-
env.EnvDefaultBackendIP,
77-
env.EnvDefaultBackendRTMP,
78-
env.EnvDefaultBackendHttp,
79-
env.EnvDefaultBackendAPI,
80-
env.EnvDefaultBackendRTC,
81-
env.EnvDefaultBackendSRT,
82-
)
83-
}
84-
85-
if err := lb.SrsLoadBalancer.Initialize(ctx); err != nil {
86-
return errors.Wrapf(err, "initialize srs load balancer")
87-
}
88-
89-
// Parse the gracefully quit timeout.
90-
gracefulQuitTimeout, err := utils.ParseGracefullyQuitTimeout()
91-
if err != nil {
92-
return errors.Wrapf(err, "parse gracefully quit timeout")
93-
}
94-
95-
// Start the RTMP server.
96-
srsRTMPServer := protocol.NewSRSRTMPServer()
97-
defer srsRTMPServer.Close()
98-
if err := srsRTMPServer.Run(ctx); err != nil {
99-
return errors.Wrapf(err, "rtmp server")
100-
}
101-
102-
// Start the WebRTC server.
103-
srsWebRTCServer := protocol.NewSRSWebRTCServer()
104-
defer srsWebRTCServer.Close()
105-
if err := srsWebRTCServer.Run(ctx); err != nil {
106-
return errors.Wrapf(err, "rtc server")
107-
}
108-
109-
// Start the HTTP API server.
110-
srsHTTPAPIServer := protocol.NewSRSHTTPAPIServer(gracefulQuitTimeout, srsWebRTCServer)
111-
defer srsHTTPAPIServer.Close()
112-
if err := srsHTTPAPIServer.Run(ctx); err != nil {
113-
return errors.Wrapf(err, "http api server")
114-
}
115-
116-
// Start the SRT server.
117-
srsSRTServer := protocol.NewSRSSRTServer()
118-
defer srsSRTServer.Close()
119-
if err := srsSRTServer.Run(ctx); err != nil {
120-
return errors.Wrapf(err, "srt server")
121-
}
122-
123-
// Start the System API server.
124-
systemAPI := protocol.NewSystemAPI(gracefulQuitTimeout)
125-
defer systemAPI.Close()
126-
if err := systemAPI.Run(ctx); err != nil {
127-
return errors.Wrapf(err, "system api server")
128-
}
129-
130-
// Start the HTTP web server.
131-
srsHTTPStreamServer := protocol.NewSRSHTTPStreamServer(gracefulQuitTimeout)
132-
defer srsHTTPStreamServer.Close()
133-
if err := srsHTTPStreamServer.Run(ctx); err != nil {
134-
return errors.Wrapf(err, "http server")
135-
}
136-
137-
// Wait for the main loop to quit.
138-
<-ctx.Done()
139-
return nil
14018
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ require github.com/go-redis/redis/v8 v8.11.5
77
require (
88
github.com/cespare/xxhash/v2 v2.1.2 // indirect
99
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
10+
github.com/joho/godotenv v1.5.1 // indirect
1011
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
55
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
66
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
77
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
8+
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
9+
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
810
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
911
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
1012
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=

internal/bootstrap/bootstrap.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Copyright (c) 2025 Winlin
2+
//
3+
// SPDX-License-Identifier: MIT
4+
package bootstrap
5+
6+
import (
7+
"context"
8+
"time"
9+
10+
"srs-proxy/internal/debug"
11+
"srs-proxy/internal/env"
12+
"srs-proxy/internal/errors"
13+
"srs-proxy/internal/lb"
14+
"srs-proxy/internal/logger"
15+
"srs-proxy/internal/protocol"
16+
"srs-proxy/internal/signal"
17+
"srs-proxy/internal/version"
18+
)
19+
20+
// Bootstrap defines the interface for application bootstrap operations.
21+
type Bootstrap interface {
22+
// Start initializes the context with logger and signal handlers, then runs the bootstrap.
23+
// Returns any error encountered during startup.
24+
Start(ctx context.Context) error
25+
26+
// Run initializes and starts all proxy servers and the load balancer.
27+
// It blocks until the context is cancelled.
28+
Run(ctx context.Context) error
29+
}
30+
31+
// bootstrapImpl implements the Bootstrap interface.
32+
type bootstrapImpl struct{}
33+
34+
// NewBootstrap creates a new Bootstrap instance.
35+
func NewBootstrap() Bootstrap {
36+
return &bootstrapImpl{}
37+
}
38+
39+
// Start initializes the context with logger and signal handlers, then runs the bootstrap.
40+
// Returns any error encountered during startup.
41+
func (b *bootstrapImpl) Start(ctx context.Context) error {
42+
ctx = logger.WithContext(ctx)
43+
logger.Df(ctx, "%v/%v started", version.Signature(), version.Version())
44+
45+
// Install signals.
46+
ctx, cancel := context.WithCancel(ctx)
47+
signal.InstallSignals(ctx, cancel)
48+
49+
// Run the main loop, ignore the user cancel error.
50+
err := b.Run(ctx)
51+
if err != nil && ctx.Err() != context.Canceled {
52+
logger.Ef(ctx, "main: %+v", err)
53+
return err
54+
}
55+
56+
logger.Df(ctx, "%v done", version.Signature())
57+
return nil
58+
}
59+
60+
// Run initializes and starts all proxy servers and the load balancer.
61+
// It blocks until the context is cancelled.
62+
func (b *bootstrapImpl) Run(ctx context.Context) error {
63+
// Setup the environment variables.
64+
environment, err := env.NewEnvironment(ctx)
65+
if err != nil {
66+
return errors.Wrapf(err, "create environment")
67+
}
68+
69+
// When cancelled, the program is forced to exit due to a timeout. Normally, this doesn't occur
70+
// because the main thread exits after the context is cancelled. However, sometimes the main thread
71+
// may be blocked for some reason, so a forced exit is necessary to ensure the program terminates.
72+
if err := signal.InstallForceQuit(ctx, environment); err != nil {
73+
return errors.Wrapf(err, "install force quit")
74+
}
75+
76+
// Start the Go pprof if enabled.
77+
debug.HandleGoPprof(ctx, environment)
78+
79+
// Initialize the load balancer.
80+
if err := b.initializeLoadBalancer(ctx, environment); err != nil {
81+
return err
82+
}
83+
84+
// Parse the gracefully quit timeout.
85+
gracefulQuitTimeout, err := time.ParseDuration(environment.GraceQuitTimeout())
86+
if err != nil {
87+
return errors.Wrapf(err, "parse gracefully quit timeout")
88+
}
89+
90+
// Start all servers and block until context is cancelled.
91+
return b.startServers(ctx, environment, gracefulQuitTimeout)
92+
}
93+
94+
// initializeLoadBalancer sets up the load balancer based on configuration.
95+
func (b *bootstrapImpl) initializeLoadBalancer(ctx context.Context, environment env.Environment) error {
96+
switch environment.LoadBalancerType() {
97+
case "redis":
98+
lb.SrsLoadBalancer = lb.NewRedisLoadBalancer(environment)
99+
default:
100+
lb.SrsLoadBalancer = lb.NewMemoryLoadBalancer(environment)
101+
}
102+
103+
if err := lb.SrsLoadBalancer.Initialize(ctx); err != nil {
104+
return errors.Wrapf(err, "initialize srs load balancer")
105+
}
106+
107+
return nil
108+
}
109+
110+
// startServers initializes and starts all protocol servers.
111+
func (b *bootstrapImpl) startServers(ctx context.Context, environment env.Environment, gracefulQuitTimeout time.Duration) error {
112+
// Start the RTMP server.
113+
srsRTMPServer := protocol.NewSRSRTMPServer(environment)
114+
if err := srsRTMPServer.Run(ctx); err != nil {
115+
return errors.Wrapf(err, "rtmp server")
116+
}
117+
defer srsRTMPServer.Close()
118+
119+
// Start the WebRTC server.
120+
srsWebRTCServer := protocol.NewSRSWebRTCServer(environment)
121+
if err := srsWebRTCServer.Run(ctx); err != nil {
122+
return errors.Wrapf(err, "rtc server")
123+
}
124+
defer srsWebRTCServer.Close()
125+
126+
// Start the HTTP API server.
127+
srsHTTPAPIServer := protocol.NewSRSHTTPAPIServer(environment, gracefulQuitTimeout, srsWebRTCServer)
128+
if err := srsHTTPAPIServer.Run(ctx); err != nil {
129+
return errors.Wrapf(err, "http api server")
130+
}
131+
defer srsHTTPAPIServer.Close()
132+
133+
// Start the SRT server.
134+
srsSRTServer := protocol.NewSRSSRTServer(environment)
135+
if err := srsSRTServer.Run(ctx); err != nil {
136+
return errors.Wrapf(err, "srt server")
137+
}
138+
defer srsSRTServer.Close()
139+
140+
// Start the System API server.
141+
systemAPI := protocol.NewSystemAPI(environment, gracefulQuitTimeout)
142+
if err := systemAPI.Run(ctx); err != nil {
143+
return errors.Wrapf(err, "system api server")
144+
}
145+
defer systemAPI.Close()
146+
147+
// Start the HTTP web server.
148+
srsHTTPStreamServer := protocol.NewSRSHTTPStreamServer(environment, gracefulQuitTimeout)
149+
if err := srsHTTPStreamServer.Run(ctx); err != nil {
150+
return errors.Wrapf(err, "http server")
151+
}
152+
defer srsHTTPStreamServer.Close()
153+
154+
// Wait for the main loop to quit.
155+
<-ctx.Done()
156+
157+
return nil
158+
}

internal/debug/pprof.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111
"srs-proxy/internal/logger"
1212
)
1313

14-
func HandleGoPprof(ctx context.Context) {
15-
if addr := env.EnvGoPprof(); addr != "" {
14+
func HandleGoPprof(ctx context.Context, environment env.Environment) {
15+
if addr := environment.GoPprof(); addr != "" {
1616
go func() {
1717
logger.Df(ctx, "Start Go pprof at %v", addr)
1818
http.ListenAndServe(addr, nil)

0 commit comments

Comments
 (0)