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}