smart_tcp.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close