v2.31.0-dev23 58a92f3

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 := &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}

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