Skip to content

Commit dbbad00

Browse files
authored
Expose /status on public HTTP interface as well (#3764)
* Expose /status on public HTTP interface as well * comments * fix logging * fix test * tests * godoc
1 parent 55a9a7e commit dbbad00

File tree

3 files changed

+83
-40
lines changed

3 files changed

+83
-40
lines changed

http/echo.go

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ import (
3333
// RootPath is the path used for routes that don't map to a configured bind.
3434
const RootPath = "/"
3535

36+
// StatusPath is the path used for the status endpoint.
37+
const StatusPath = "/status"
38+
39+
// MetricsPath is the path used for the metrics endpoint.
40+
const MetricsPath = "/metrics"
41+
42+
// HealthPath is the path used for the health endpoint.
43+
const HealthPath = "/health"
44+
45+
// InternalPath is the path used for internal endpoints.
46+
const InternalPath = "/internal"
47+
3648
// EchoCreator is a function used to create an Echo server.
3749
type EchoCreator func() (EchoServer, error)
3850

@@ -41,21 +53,24 @@ type EchoCreator func() (EchoServer, error)
4153
func NewMultiEcho() *MultiEcho {
4254
instance := &MultiEcho{
4355
interfaces: map[string]EchoServer{},
44-
binds: map[string]string{},
56+
binds: map[string][]string{},
4557
}
4658

4759
// Add adds a route to the Echo server.
4860
instance.echoAdapter.useFn = instance.Use
4961
instance.echoAdapter.addFn = func(method, path string, handler echo.HandlerFunc, middleware ...echo.MiddlewareFunc) *echo.Route {
5062
bind := instance.getBindFromPath(path)
51-
bindAddress := instance.binds[bind]
52-
var iface EchoServer
53-
if bindAddress != "" {
54-
iface = instance.interfaces[bindAddress]
55-
} else {
56-
iface = instance.interfaces[instance.binds[RootPath]]
63+
bindAddresses := instance.binds[bind]
64+
// If not bound to a specific HTTP interface, bind to HTTP interface associated with root HTTP path (/)
65+
if len(bindAddresses) == 0 {
66+
bindAddresses = instance.binds[RootPath]
67+
}
68+
var route *echo.Route
69+
for _, bindAddress := range bindAddresses {
70+
route = instance.interfaces[bindAddress].Add(method, path, handler, middleware...)
5771
}
58-
return iface.Add(method, path, handler, middleware...)
72+
// Doesn't matter from which HTTP interface we return the route, since they're all the same.
73+
return route
5974
}
6075
return instance
6176
}
@@ -65,16 +80,19 @@ type MultiEcho struct {
6580
echoAdapter
6681

6782
interfaces map[string]EchoServer
68-
binds map[string]string
83+
binds map[string][]string
6984
}
7085

71-
// Bind binds the given path (first part of the URL) to the given HTTP interface. Calling Bind for the same path twice
86+
// Bind binds the given path (first part of the URL) to the given HTTP interfaces. Calling Bind for the same path twice
7287
// results in an error being returned.
73-
// If address wasn't used for another bind and thus leads to creating a new Echo server, it returns true.
74-
// If an existing Echo server is returned, it returns false.
75-
func (c *MultiEcho) Bind(path string, address string, creatorFn func(ipHeader string) (EchoServer, error), ipHeader string) error {
76-
if len(address) == 0 {
77-
return errors.New("empty address")
88+
func (c *MultiEcho) Bind(path string, addresses []string, creatorFn func(ipHeader string) (EchoServer, error), ipHeader string) error {
89+
if len(addresses) == 0 {
90+
return errors.New("no addresses")
91+
}
92+
for _, address := range addresses {
93+
if len(address) == 0 {
94+
return errors.New("empty address")
95+
}
7896
}
7997
err := c.validateBindPath(path)
8098
if err != nil {
@@ -84,13 +102,16 @@ func (c *MultiEcho) Bind(path string, address string, creatorFn func(ipHeader st
84102
if _, pathExists := c.binds[path]; pathExists {
85103
return fmt.Errorf("http bind already exists: %s", path)
86104
}
87-
c.binds[path] = address
88-
if _, addressBound := c.interfaces[address]; !addressBound {
89-
server, err := creatorFn(ipHeader)
90-
if err != nil {
91-
return err
105+
c.binds[path] = addresses
106+
107+
for _, address := range addresses {
108+
if _, addressBound := c.interfaces[address]; !addressBound {
109+
server, err := creatorFn(ipHeader)
110+
if err != nil {
111+
return err
112+
}
113+
c.interfaces[address] = server
92114
}
93-
c.interfaces[address] = server
94115
}
95116
return nil
96117
}
@@ -136,7 +157,7 @@ func (c MultiEcho) Use(middleware ...echo.MiddlewareFunc) {
136157
curr.Use(middleware...)
137158
}
138159
}
139-
func (c *MultiEcho) getAddressForPath(path string) string {
160+
func (c *MultiEcho) getAddressesForPath(path string) []string {
140161
return c.binds[c.getBindFromPath(path)]
141162
}
142163

http/echo_test.go

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,31 @@ func Test_MultiEcho_Bind(t *testing.T) {
3232
const defaultAddress = ":1323"
3333
t.Run("group already bound", func(t *testing.T) {
3434
m := NewMultiEcho()
35-
err := m.Bind("", defaultAddress, func(ipHeader string) (EchoServer, error) {
35+
err := m.Bind("", []string{defaultAddress}, func(ipHeader string) (EchoServer, error) {
3636
return echo.New(), nil
3737
}, "header")
3838
require.NoError(t, err)
39-
err = m.Bind("", defaultAddress, func(ipHeader string) (EchoServer, error) {
39+
err = m.Bind("", []string{defaultAddress}, func(ipHeader string) (EchoServer, error) {
4040
return echo.New(), nil
4141
}, "header")
4242
assert.EqualError(t, err, "http bind already exists: /")
4343
})
4444
t.Run("error - group contains subpaths", func(t *testing.T) {
4545
m := NewMultiEcho()
46-
err := m.Bind("internal/vdr", defaultAddress, nil, "")
46+
err := m.Bind("internal/vdr", []string{defaultAddress}, nil, "")
4747
assert.EqualError(t, err, "bind can't contain subpaths: internal/vdr")
4848
})
49+
t.Run("empty address", func(t *testing.T) {
50+
m := NewMultiEcho()
51+
err := m.Bind("internal", []string{""}, nil, "")
52+
assert.EqualError(t, err, "empty address")
53+
})
54+
t.Run("no addresses", func(t *testing.T) {
55+
m := NewMultiEcho()
56+
err := m.Bind("internal", []string{}, nil, "")
57+
assert.EqualError(t, err, "no addresses")
58+
})
59+
4960
}
5061

5162
func Test_MultiEcho_Start(t *testing.T) {
@@ -55,7 +66,7 @@ func Test_MultiEcho_Start(t *testing.T) {
5566
server.EXPECT().Start(gomock.Any()).Return(errors.New("unable to start"))
5667

5768
m := NewMultiEcho()
58-
m.Bind("group2", ":8080", func(ipHeader string) (EchoServer, error) {
69+
m.Bind("group2", []string{":8080"}, func(ipHeader string) (EchoServer, error) {
5970
return server, nil
6071
}, "header")
6172
err := m.Start()
@@ -74,35 +85,42 @@ func Test_MultiEcho(t *testing.T) {
7485
defaultServer.EXPECT().Start(defaultAddress)
7586

7687
internalServer := NewMockEchoServer(ctrl)
77-
internalServer.EXPECT().Add("GET", "/internal/internal-endpoint", gomock.Any())
88+
internalServer.EXPECT().Add(http.MethodGet, "/internal/internal-endpoint", gomock.Any())
89+
internalServer.EXPECT().Add(http.MethodGet, "/status", gomock.Any())
7890
internalServer.EXPECT().Start("internal:8080")
7991

8092
publicServer := NewMockEchoServer(ctrl)
8193
publicServer.EXPECT().Add(http.MethodPost, "/public/pub-endpoint", gomock.Any())
8294
publicServer.EXPECT().Add(http.MethodDelete, "/extra-public/extra-pub-endpoint", gomock.Any())
95+
publicServer.EXPECT().Add(http.MethodGet, "/status", gomock.Any())
8396
publicServer.EXPECT().Start("public:8080")
8497

8598
// Bind interfaces
8699
m := NewMultiEcho()
87-
err := m.Bind(RootPath, defaultAddress, func(ipHeader string) (EchoServer, error) {
100+
err := m.Bind(RootPath, []string{defaultAddress}, func(ipHeader string) (EchoServer, error) {
88101
return defaultServer, nil
89102
}, "header")
90103
require.NoError(t, err)
91-
err = m.Bind("internal", "internal:8080", func(ipHeader string) (EchoServer, error) {
104+
err = m.Bind("internal", []string{"internal:8080"}, func(ipHeader string) (EchoServer, error) {
92105
return internalServer, nil
93106
}, "header")
94107
require.NoError(t, err)
95-
err = m.Bind("public", "public:8080", func(ipHeader string) (EchoServer, error) {
108+
err = m.Bind("public", []string{"public:8080"}, func(ipHeader string) (EchoServer, error) {
96109
return publicServer, nil
97110
}, "header")
98111
require.NoError(t, err)
99-
err = m.Bind("extra-public", "public:8080", func(ipHeader string) (EchoServer, error) {
112+
err = m.Bind("extra-public", []string{"public:8080"}, func(ipHeader string) (EchoServer, error) {
100113
t.Fatal("should not be called!")
101114
return nil, nil
102115
}, "header")
103116
require.NoError(t, err)
117+
err = m.Bind("status", []string{"public:8080", "internal:8080"}, func(ipHeader string) (EchoServer, error) {
118+
return internalServer, nil
119+
}, "header")
120+
require.NoError(t, err)
104121

105122
m.addFn(http.MethodPost, "/public/pub-endpoint", nil)
123+
m.addFn(http.MethodGet, "/status", nil)
106124
m.addFn(http.MethodDelete, "/extra-public/extra-pub-endpoint", nil)
107125
m.addFn(http.MethodGet, "/internal/internal-endpoint", nil)
108126
m.addFn(http.MethodPatch, "/other/default-endpoint", nil)
@@ -129,7 +147,7 @@ func Test_MultiEcho_Methods(t *testing.T) {
129147
)
130148

131149
m := NewMultiEcho()
132-
m.Bind(RootPath, ":1323", func(ipHeader string) (EchoServer, error) {
150+
m.Bind(RootPath, []string{":1323"}, func(ipHeader string) (EchoServer, error) {
133151
return defaultServer, nil
134152
}, "header")
135153
m.GET("/get", nil)

http/engine.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,23 @@ func (h *Engine) Configure(serverConfig core.ServerConfig) error {
7676

7777
h.server = NewMultiEcho()
7878
// Public endpoints
79-
if err := h.server.Bind(RootPath, h.config.Public.Address, h.createEchoServer, h.config.ClientIPHeaderName); err != nil {
79+
if err := h.server.Bind(RootPath, []string{h.config.Public.Address}, h.createEchoServer, h.config.ClientIPHeaderName); err != nil {
8080
return err
8181
}
8282
// Internal endpoints
83-
for _, httpPath := range []string{"/internal", "/status", "/health", "/metrics"} {
84-
if err := h.server.Bind(httpPath, h.config.Internal.Address, h.createEchoServer, h.config.ClientIPHeaderName); err != nil {
83+
for _, httpPath := range []string{InternalPath, HealthPath, MetricsPath} {
84+
if err := h.server.Bind(httpPath, []string{h.config.Internal.Address}, h.createEchoServer, h.config.ClientIPHeaderName); err != nil {
8585
return err
8686
}
8787
}
88+
// /status endpoint is both on internally and publicly available.
89+
if err := h.server.Bind(StatusPath, []string{h.config.Public.Address, h.config.Internal.Address}, h.createEchoServer, h.config.ClientIPHeaderName); err != nil {
90+
return err
91+
}
8892

8993
h.applyRateLimiterMiddleware(h.server, serverConfig)
90-
h.applyLoggerMiddleware(h.server, []string{"/metrics", "/status", "/health"}, h.config.Log)
91-
return h.applyAuthMiddleware(h.server, "/internal", h.config.Internal.Auth)
94+
h.applyLoggerMiddleware(h.server, []string{MetricsPath, StatusPath, HealthPath}, h.config.Log)
95+
return h.applyAuthMiddleware(h.server, InternalPath, h.config.Internal.Auth)
9296
}
9397

9498
func (h *Engine) configureClient(serverConfig core.ServerConfig) {
@@ -218,8 +222,6 @@ func (h Engine) applyLoggerMiddleware(echoServer core.EchoRouter, excludePaths [
218222
}
219223

220224
func (h Engine) applyAuthMiddleware(echoServer core.EchoRouter, path string, config AuthConfig) error {
221-
address := h.server.getAddressForPath(path)
222-
223225
skipper := func(c echo.Context) bool {
224226
return !matchesPath(c.Request().RequestURI, path)
225227
}
@@ -231,7 +233,9 @@ func (h Engine) applyAuthMiddleware(echoServer core.EchoRouter, path string, con
231233
return nil
232234

233235
case BearerTokenAuthV2:
234-
log.Logger().Infof("Enabling token authentication (v2) for HTTP interface: %s%s", address, path)
236+
for _, address := range h.server.getAddressesForPath(path) {
237+
log.Logger().Infof("Enabling token authentication (v2) for HTTP interface: %s%s", address, path)
238+
}
235239

236240
// Use the configured audience or the hostname by default
237241
audience := config.Audience

0 commit comments

Comments
 (0)