v2.30.1 1983e1b

forwarder_test.go RawHistoryFolder

1package proxy
2
3import (
4	"fmt"
5	"net"
6	"net/http"
7	"net/http/httptest"
8	"strconv"
9	"testing"
10
11	"github.com/bnema/zerowrap"
12	"github.com/stretchr/testify/assert"
13	"github.com/stretchr/testify/mock"
14	"github.com/stretchr/testify/require"
15
16	"github.com/bnema/gordon/internal/boundaries/in"
17	inmocks "github.com/bnema/gordon/internal/boundaries/in/mocks"
18	"github.com/bnema/gordon/internal/domain"
19)
20
21func TestForwardToTarget_H2CEndToEnd(t *testing.T) {
22	var h2cProtos http.Protocols
23	h2cProtos.SetUnencryptedHTTP2(true)
24
25	h2cBackend := &http.Server{
26		Protocols: &h2cProtos,
27		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
28			w.Header().Set("X-Proto", r.Proto)
29			w.WriteHeader(http.StatusOK)
30			_, _ = fmt.Fprintf(w, "proto=%s", r.Proto)
31		}),
32	}
33
34	ln, err := net.Listen("tcp", "127.0.0.1:0")
35	require.NoError(t, err)
36	defer ln.Close()
37
38	go func() {
39		_ = h2cBackend.Serve(ln)
40	}()
41	defer h2cBackend.Close()
42
43	_, portStr, splitErr := net.SplitHostPort(ln.Addr().String())
44	require.NoError(t, splitErr)
45	port, atoiErr := strconv.Atoi(portStr)
46	require.NoError(t, atoiErr)
47
48	proxySvc := inmocks.NewMockProxyService(t)
49
50	proxySvc.EXPECT().ProxyConfig().Return(in.ProxyServiceConfig{})
51	proxySvc.EXPECT().IsRegistryDomain("grpc.example.com").Return(false)
52	proxySvc.EXPECT().GetTarget(mock.Anything, "grpc.example.com").Return(&domain.ProxyTarget{
53		Host:        "127.0.0.1",
54		Port:        port,
55		ContainerID: "grpc-1",
56		Scheme:      "http",
57		Protocol:    "h2c",
58	}, nil)
59	proxySvc.EXPECT().TrackInFlight("grpc-1").Return(func() {})
60
61	handler := NewHandler(proxySvc, zerowrap.Default())
62
63	req := httptest.NewRequest(http.MethodGet, "http://grpc.example.com/test", nil)
64	req.Host = "grpc.example.com"
65	rec := httptest.NewRecorder()
66
67	handler.ServeHTTP(rec, req)
68
69	assert.Equal(t, http.StatusOK, rec.Code)
70	assert.Contains(t, rec.Body.String(), "HTTP/2")
71}
72
73func TestForwardToTarget_HTTP1StillWorks(t *testing.T) {
74	backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
75		_, _ = fmt.Fprintf(w, "proto=%s", r.Proto)
76	}))
77	defer backend.Close()
78
79	backendPort := backend.Listener.Addr().(*net.TCPAddr).Port
80
81	proxySvc := inmocks.NewMockProxyService(t)
82
83	proxySvc.EXPECT().ProxyConfig().Return(in.ProxyServiceConfig{})
84	proxySvc.EXPECT().IsRegistryDomain("web.example.com").Return(false)
85	proxySvc.EXPECT().GetTarget(mock.Anything, "web.example.com").Return(&domain.ProxyTarget{
86		Host:        "127.0.0.1",
87		Port:        backendPort,
88		ContainerID: "web-1",
89		Scheme:      "http",
90	}, nil)
91	proxySvc.EXPECT().TrackInFlight("web-1").Return(func() {})
92
93	handler := NewHandler(proxySvc, zerowrap.Default())
94
95	req := httptest.NewRequest(http.MethodGet, "http://web.example.com/", nil)
96	req.Host = "web.example.com"
97	rec := httptest.NewRecorder()
98
99	handler.ServeHTTP(rec, req)
100
101	assert.Equal(t, http.StatusOK, rec.Code)
102	assert.Contains(t, rec.Body.String(), "HTTP/1.1")
103}
104
105func TestTransportForTarget_SelectsCorrectTransport(t *testing.T) {
106	handler := NewHandler(inmocks.NewMockProxyService(t), zerowrap.Default())
107
108	h2cTarget := &domain.ProxyTarget{Protocol: "h2c"}
109	httpTarget := &domain.ProxyTarget{Protocol: ""}
110
111	assert.Equal(t, handler.h2cTransport, handler.transportForTarget(h2cTarget))
112	assert.Equal(t, handler.appTransport, handler.transportForTarget(httpTarget))
113}
114
115func TestForwardToTarget_OriginalHostPreserved(t *testing.T) {
116	var receivedHost string
117	backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
118		receivedHost = r.Host
119		w.WriteHeader(http.StatusOK)
120	}))
121	defer backend.Close()
122
123	backendPort := backend.Listener.Addr().(*net.TCPAddr).Port
124
125	proxySvc := inmocks.NewMockProxyService(t)
126
127	proxySvc.EXPECT().ProxyConfig().Return(in.ProxyServiceConfig{})
128	proxySvc.EXPECT().IsRegistryDomain("external.example.com").Return(false)
129	proxySvc.EXPECT().GetTarget(mock.Anything, "external.example.com").Return(&domain.ProxyTarget{
130		Host:         "127.0.0.1",
131		Port:         backendPort,
132		ContainerID:  "",
133		Scheme:       "http",
134		OriginalHost: "upstream.example.com",
135	}, nil)
136	proxySvc.EXPECT().TrackInFlight("").Return(func() {})
137
138	handler := NewHandler(proxySvc, zerowrap.Default())
139
140	req := httptest.NewRequest(http.MethodGet, "http://external.example.com/", nil)
141	req.Host = "external.example.com"
142	w := httptest.NewRecorder()
143	handler.ServeHTTP(w, req)
144
145	assert.Equal(t, http.StatusOK, w.Code)
146	assert.Contains(t, receivedHost, "upstream.example.com")
147}
148
149func TestForwardToTarget_BackendDown_Returns503(t *testing.T) {
150	proxySvc := inmocks.NewMockProxyService(t)
151
152	proxySvc.EXPECT().ProxyConfig().Return(in.ProxyServiceConfig{})
153	proxySvc.EXPECT().IsRegistryDomain("down.example.com").Return(false)
154	proxySvc.EXPECT().GetTarget(mock.Anything, "down.example.com").Return(&domain.ProxyTarget{
155		Host:        "127.0.0.1",
156		Port:        1,
157		ContainerID: "c-down",
158		Scheme:      "http",
159	}, nil)
160	proxySvc.EXPECT().TrackInFlight("c-down").Return(func() {})
161
162	handler := NewHandler(proxySvc, zerowrap.Default())
163
164	req := httptest.NewRequest(http.MethodGet, "http://down.example.com/", nil)
165	req.Host = "down.example.com"
166	w := httptest.NewRecorder()
167	handler.ServeHTTP(w, req)
168
169	assert.Equal(t, http.StatusServiceUnavailable, w.Code)
170}
171
172func TestForwardToTarget_ProxyHeaderSet(t *testing.T) {
173	backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
174		w.WriteHeader(http.StatusOK)
175	}))
176	defer backend.Close()
177
178	backendPort := backend.Listener.Addr().(*net.TCPAddr).Port
179
180	proxySvc := inmocks.NewMockProxyService(t)
181
182	proxySvc.EXPECT().ProxyConfig().Return(in.ProxyServiceConfig{})
183	proxySvc.EXPECT().IsRegistryDomain("app.example.com").Return(false)
184	proxySvc.EXPECT().GetTarget(mock.Anything, "app.example.com").Return(&domain.ProxyTarget{
185		Host:        "127.0.0.1",
186		Port:        backendPort,
187		ContainerID: "c-1",
188		Scheme:      "http",
189	}, nil)
190	proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})
191
192	handler := NewHandler(proxySvc, zerowrap.Default())
193
194	req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)
195	req.Host = "app.example.com"
196	w := httptest.NewRecorder()
197	handler.ServeHTTP(w, req)
198
199	assert.Equal(t, http.StatusOK, w.Code)
200}

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