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 := newEntryPointHTTPServer(config.Handler, r.manager.httpOptions())
155 server.Protocols = config.Protocols
156 done := make(chan struct{})
157 r.mu.Lock()
158 oldListener := r.smartHTTPListener
159 oldServer := r.smartHTTPServer
160 oldDone := r.smartHTTPDone
161 if r.isClosed() {
162 r.mu.Unlock()
163 _ = listener.Close()
164 return
165 }
166 r.smartHTTPListener = listener
167 r.smartHTTPServer = server
168 r.smartHTTPDone = done
169 r.mu.Unlock()
170 go func() {
171 defer close(done)
172 if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) && !errors.Is(err, net.ErrClosed) {
173 trafficWarn(r.ctx).Err(err).Str("entrypoint", entryPoint.Name).Msg("smart tcp http server stopped with error")
174 }
175 }()
176 stopInternalHTTPServer(r.ctx, oldListener, oldServer, oldDone, 0)
177}
178
179func (r *entryPointRuntime) replaceSmartTCPTLSServer(entryPoint domain.EntryPoint, config SmartTCPTLSServerConfig) {
180 r.smartTLSReplaceMu.Lock()
181 defer r.smartTLSReplaceMu.Unlock()
182 if r.isClosed() {
183 return
184 }
185 listener := newTLSHTTPListener(r.listener.Addr())
186 server := newEntryPointHTTPServer(config.Handler, r.manager.httpOptions())
187 server.TLSConfig = config.TLSConfig.Clone()
188 done := make(chan struct{})
189 r.mu.Lock()
190 oldListener := r.smartTLSListener
191 oldServer := r.smartTLSServer
192 oldDone := r.smartTLSDone
193 if r.isClosed() {
194 r.mu.Unlock()
195 _ = listener.Close()
196 return
197 }
198 r.smartTLSListener = listener
199 r.smartTLSServer = server
200 r.smartTLSDone = done
201 r.mu.Unlock()
202 go func() {
203 defer close(done)
204 if err := server.ServeTLS(listener, "", ""); err != nil && !errors.Is(err, http.ErrServerClosed) && !errors.Is(err, net.ErrClosed) {
205 trafficWarn(r.ctx).Err(err).Str("entrypoint", entryPoint.Name).Msg("smart tcp https server stopped with error")
206 }
207 }()
208 stopInternalHTTPServer(r.ctx, oldListener, oldServer, oldDone, 0)
209}
210
211func (r *entryPointRuntime) stopSmartTCPHTTPServer(ctx context.Context, drainTimeout time.Duration) {
212 r.smartHTTPReplaceMu.Lock()
213 defer r.smartHTTPReplaceMu.Unlock()
214 r.stopSmartTCPHTTPServerLocked(ctx, drainTimeout)
215}
216
217func (r *entryPointRuntime) stopSmartTCPHTTPServerLocked(ctx context.Context, drainTimeout time.Duration) {
218 r.mu.Lock()
219 listener := r.smartHTTPListener
220 server := r.smartHTTPServer
221 done := r.smartHTTPDone
222 r.smartHTTPListener = nil
223 r.smartHTTPServer = nil
224 r.smartHTTPDone = nil
225 r.mu.Unlock()
226 stopInternalHTTPServer(ctx, listener, server, done, drainTimeout)
227}
228
229func (r *entryPointRuntime) stopSmartTCPTLSServer(ctx context.Context, drainTimeout time.Duration) {
230 r.smartTLSReplaceMu.Lock()
231 defer r.smartTLSReplaceMu.Unlock()
232 r.stopSmartTCPTLSServerLocked(ctx, drainTimeout)
233}
234
235func (r *entryPointRuntime) stopSmartTCPTLSServerLocked(ctx context.Context, drainTimeout time.Duration) {
236 r.mu.Lock()
237 listener := r.smartTLSListener
238 server := r.smartTLSServer
239 done := r.smartTLSDone
240 r.smartTLSListener = nil
241 r.smartTLSServer = nil
242 r.smartTLSDone = nil
243 r.mu.Unlock()
244 stopInternalHTTPServer(ctx, listener, server, done, drainTimeout)
245}
246
247func stopInternalHTTPServer(ctx context.Context, listener *tlsHTTPListener, server *http.Server, done chan struct{}, drainTimeout time.Duration) {
248 if listener == nil || server == nil || done == nil {
249 return
250 }
251 _ = listener.Close()
252 if drainTimeout <= 0 {
253 drainTimeout = defaultTCPOptions().DrainTimeout
254 }
255 shutdownCtx, cancel := context.WithTimeout(ctx, drainTimeout)
256 defer cancel()
257 if err := server.Shutdown(shutdownCtx); err != nil {
258 _ = server.Close()
259 }
260 select {
261 case <-done:
262 case <-shutdownCtx.Done():
263 }
264}