1package traffic
2
3import (
4 "context"
5 "crypto/tls"
6 "errors"
7 "maps"
8 "net"
9 "net/http"
10 "time"
11
12 "github.com/bnema/gordon/internal/domain"
13)
14
15type SmartTCPHTTPServerConfig struct {
16 Handler http.Handler
17 Protocols *http.Protocols
18}
19
20type SmartTCPTLSServerConfig struct {
21 Handler http.Handler
22 TLSConfig *tls.Config
23}
24
25type smartHTTPServers map[string]SmartTCPHTTPServerConfig
26type smartTLSServers map[string]SmartTCPTLSServerConfig
27
28func (m *Manager) SetSmartTCPHTTPServer(entryPoint string, handler http.Handler, protocols *http.Protocols) {
29 m.mu.Lock()
30 current := m.loadSmartHTTPServers()
31 next := make(smartHTTPServers, len(current)+1)
32 maps.Copy(next, current)
33 if handler == nil {
34 delete(next, entryPoint)
35 } else {
36 next[entryPoint] = SmartTCPHTTPServerConfig{Handler: handler, Protocols: cloneHTTPProtocols(protocols)}
37 }
38 m.smartHTTPServers.Store(next)
39 runtimes := m.smartTCPRuntimesLocked(entryPoint)
40 m.mu.Unlock()
41 for _, runtime := range runtimes {
42 runtime.refreshSmartTCPHTTPServer(entryPoint)
43 }
44}
45
46func cloneHTTPProtocols(protocols *http.Protocols) *http.Protocols {
47 if protocols == nil {
48 return nil
49 }
50 clone := *protocols
51 return &clone
52}
53
54func (m *Manager) SetSmartTCPTLSServer(entryPoint string, handler http.Handler, tlsConfig *tls.Config) {
55 m.mu.Lock()
56 current := m.loadSmartTLSServers()
57 next := make(smartTLSServers, len(current)+1)
58 maps.Copy(next, current)
59 if handler == nil || tlsConfig == nil {
60 delete(next, entryPoint)
61 } else {
62 next[entryPoint] = SmartTCPTLSServerConfig{Handler: handler, TLSConfig: tlsConfig.Clone()}
63 }
64 m.smartTLSServers.Store(next)
65 runtimes := m.smartTCPRuntimesLocked(entryPoint)
66 m.mu.Unlock()
67 for _, runtime := range runtimes {
68 runtime.refreshSmartTCPTLSServer(entryPoint)
69 }
70}
71
72func (m *Manager) smartTCPRuntimesLocked(entryPoint string) []*entryPointRuntime {
73 runtimes := make([]*entryPointRuntime, 0, len(m.listeners))
74 for _, runtime := range m.listeners {
75 snapshot := runtime.entryPointSnapshot()
76 if snapshot.Name == entryPoint && snapshot.Protocol == domain.EntryPointProtocolSmartTCP {
77 runtimes = append(runtimes, runtime)
78 }
79 }
80 return runtimes
81}
82
83func (m *Manager) smartHTTPServer(entryPoint string) (SmartTCPHTTPServerConfig, bool) {
84 config, ok := m.loadSmartHTTPServers()[entryPoint]
85 return config, ok
86}
87
88func (m *Manager) smartTLSServer(entryPoint string) (SmartTCPTLSServerConfig, bool) {
89 config, ok := m.loadSmartTLSServers()[entryPoint]
90 return config, ok
91}
92
93func (m *Manager) loadSmartHTTPServers() smartHTTPServers {
94 value := m.smartHTTPServers.Load()
95 if value == nil {
96 return smartHTTPServers{}
97 }
98 return value.(smartHTTPServers)
99}
100
101func (m *Manager) loadSmartTLSServers() smartTLSServers {
102 value := m.smartTLSServers.Load()
103 if value == nil {
104 return smartTLSServers{}
105 }
106 return value.(smartTLSServers)
107}
108
109func (r *entryPointRuntime) startSmartTCPHTTPServers(entryPoint domain.EntryPoint) {
110 if entryPoint.Protocol != domain.EntryPointProtocolSmartTCP {
111 return
112 }
113 if config, ok := r.manager.smartHTTPServer(entryPoint.Name); ok && config.Handler != nil {
114 r.replaceSmartTCPHTTPServer(entryPoint, config)
115 }
116 if config, ok := r.manager.smartTLSServer(entryPoint.Name); ok && config.Handler != nil && config.TLSConfig != nil {
117 r.replaceSmartTCPTLSServer(entryPoint, config)
118 }
119}
120
121func (r *entryPointRuntime) refreshSmartTCPHTTPServer(entryPointName string) {
122 entryPoint := r.entryPointSnapshot()
123 if entryPoint.Name != entryPointName || entryPoint.Protocol != domain.EntryPointProtocolSmartTCP {
124 return
125 }
126 config, ok := r.manager.smartHTTPServer(entryPointName)
127 if !ok || config.Handler == nil {
128 r.stopSmartTCPHTTPServer(r.ctx, 0)
129 return
130 }
131 r.replaceSmartTCPHTTPServer(entryPoint, config)
132}
133
134func (r *entryPointRuntime) refreshSmartTCPTLSServer(entryPointName string) {
135 entryPoint := r.entryPointSnapshot()
136 if entryPoint.Name != entryPointName || entryPoint.Protocol != domain.EntryPointProtocolSmartTCP {
137 return
138 }
139 config, ok := r.manager.smartTLSServer(entryPointName)
140 if !ok || config.Handler == nil || config.TLSConfig == nil {
141 r.stopSmartTCPTLSServer(r.ctx, 0)
142 return
143 }
144 r.replaceSmartTCPTLSServer(entryPoint, config)
145}
146
147func (r *entryPointRuntime) replaceSmartTCPHTTPServer(entryPoint domain.EntryPoint, config SmartTCPHTTPServerConfig) {
148 r.smartHTTPReplaceMu.Lock()
149 defer r.smartHTTPReplaceMu.Unlock()
150 if r.isClosed() {
151 return
152 }
153 listener := newTLSHTTPListener(r.listener.Addr())
154 server := &http.Server{Handler: config.Handler, Protocols: config.Protocols, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 30 * time.Second}
155 done := make(chan struct{})
156 r.mu.Lock()
157 oldListener := r.smartHTTPListener
158 oldServer := r.smartHTTPServer
159 oldDone := r.smartHTTPDone
160 if r.isClosed() {
161 r.mu.Unlock()
162 _ = listener.Close()
163 return
164 }
165 r.smartHTTPListener = listener
166 r.smartHTTPServer = server
167 r.smartHTTPDone = done
168 r.mu.Unlock()
169 go func() {
170 defer close(done)
171 if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) && !errors.Is(err, net.ErrClosed) {
172 trafficWarn(r.ctx).Err(err).Str("entrypoint", entryPoint.Name).Msg("smart tcp http server stopped with error")
173 }
174 }()
175 stopInternalHTTPServer(r.ctx, oldListener, oldServer, oldDone, 0)
176}
177
178func (r *entryPointRuntime) replaceSmartTCPTLSServer(entryPoint domain.EntryPoint, config SmartTCPTLSServerConfig) {
179 r.smartTLSReplaceMu.Lock()
180 defer r.smartTLSReplaceMu.Unlock()
181 if r.isClosed() {
182 return
183 }
184 listener := newTLSHTTPListener(r.listener.Addr())
185 server := &http.Server{Handler: config.Handler, TLSConfig: config.TLSConfig.Clone(), ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 30 * time.Second}
186 done := make(chan struct{})
187 r.mu.Lock()
188 oldListener := r.smartTLSListener
189 oldServer := r.smartTLSServer
190 oldDone := r.smartTLSDone
191 if r.isClosed() {
192 r.mu.Unlock()
193 _ = listener.Close()
194 return
195 }
196 r.smartTLSListener = listener
197 r.smartTLSServer = server
198 r.smartTLSDone = done
199 r.mu.Unlock()
200 go func() {
201 defer close(done)
202 if err := server.ServeTLS(listener, "", ""); err != nil && !errors.Is(err, http.ErrServerClosed) && !errors.Is(err, net.ErrClosed) {
203 trafficWarn(r.ctx).Err(err).Str("entrypoint", entryPoint.Name).Msg("smart tcp https server stopped with error")
204 }
205 }()
206 stopInternalHTTPServer(r.ctx, oldListener, oldServer, oldDone, 0)
207}
208
209func (r *entryPointRuntime) stopSmartTCPHTTPServer(ctx context.Context, drainTimeout time.Duration) {
210 r.smartHTTPReplaceMu.Lock()
211 defer r.smartHTTPReplaceMu.Unlock()
212 r.stopSmartTCPHTTPServerLocked(ctx, drainTimeout)
213}
214
215func (r *entryPointRuntime) stopSmartTCPHTTPServerLocked(ctx context.Context, drainTimeout time.Duration) {
216 r.mu.Lock()
217 listener := r.smartHTTPListener
218 server := r.smartHTTPServer
219 done := r.smartHTTPDone
220 r.smartHTTPListener = nil
221 r.smartHTTPServer = nil
222 r.smartHTTPDone = nil
223 r.mu.Unlock()
224 stopInternalHTTPServer(ctx, listener, server, done, drainTimeout)
225}
226
227func (r *entryPointRuntime) stopSmartTCPTLSServer(ctx context.Context, drainTimeout time.Duration) {
228 r.smartTLSReplaceMu.Lock()
229 defer r.smartTLSReplaceMu.Unlock()
230 r.stopSmartTCPTLSServerLocked(ctx, drainTimeout)
231}
232
233func (r *entryPointRuntime) stopSmartTCPTLSServerLocked(ctx context.Context, drainTimeout time.Duration) {
234 r.mu.Lock()
235 listener := r.smartTLSListener
236 server := r.smartTLSServer
237 done := r.smartTLSDone
238 r.smartTLSListener = nil
239 r.smartTLSServer = nil
240 r.smartTLSDone = nil
241 r.mu.Unlock()
242 stopInternalHTTPServer(ctx, listener, server, done, drainTimeout)
243}
244
245func stopInternalHTTPServer(ctx context.Context, listener *tlsHTTPListener, server *http.Server, done chan struct{}, drainTimeout time.Duration) {
246 if listener == nil || server == nil || done == nil {
247 return
248 }
249 _ = listener.Close()
250 if drainTimeout <= 0 {
251 drainTimeout = defaultTCPOptions().DrainTimeout
252 }
253 shutdownCtx, cancel := context.WithTimeout(ctx, drainTimeout)
254 defer cancel()
255 if err := server.Shutdown(shutdownCtx); err != nil {
256 _ = server.Close()
257 }
258 select {
259 case <-done:
260 case <-shutdownCtx.Done():
261 }
262}