Skip to content

Commit 44711cc

Browse files
YangKeaodjshow832
andauthored
api: allow HTTP health check with TLS config (#1153)
Co-authored-by: djshow832 <zhangming@pingcap.com>
1 parent d052629 commit 44711cc

4 files changed

Lines changed: 83 additions & 18 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ require (
2727
github.com/prometheus/client_model v0.6.2
2828
github.com/prometheus/common v0.63.0
2929
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726
30+
github.com/soheilhy/cmux v0.1.5
3031
github.com/spf13/cobra v1.9.1
3132
github.com/stretchr/testify v1.11.1
3233
github.com/tidwall/btree v1.7.0
@@ -232,7 +233,6 @@ require (
232233
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
233234
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd // indirect
234235
github.com/sirupsen/logrus v1.9.3 // indirect
235-
github.com/soheilhy/cmux v0.1.5 // indirect
236236
github.com/spf13/pflag v1.0.7 // indirect
237237
github.com/spkg/bom v1.0.0 // indirect
238238
github.com/tiancaiamao/appdash v0.0.0-20181126055449-889f96f722a2 // indirect

pkg/server/api/debug_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,21 @@ func TestDebugHealthManualOverride(t *testing.T) {
9393
})
9494
assertHealth(http.StatusBadGateway, "server is not ready")
9595
}
96+
97+
func TestDebugHealthAllowsHTTPWithHTTPTLS(t *testing.T) {
98+
_, doHTTP, doHTTPS := createServerWithConfig(t, `security.server-http-tls.auto-certs = true`)
99+
100+
doHTTP(t, http.MethodGet, "/api/debug/health", httpOpts{}, func(t *testing.T, r *http.Response) {
101+
require.Equal(t, http.StatusOK, r.StatusCode)
102+
})
103+
doHTTP(t, http.MethodGet, "/debug/health", httpOpts{}, func(t *testing.T, r *http.Response) {
104+
require.Equal(t, http.StatusOK, r.StatusCode)
105+
})
106+
107+
doHTTPS(t, http.MethodGet, "/api/debug/health", httpOpts{}, func(t *testing.T, r *http.Response) {
108+
require.Equal(t, http.StatusOK, r.StatusCode)
109+
})
110+
doHTTPS(t, http.MethodGet, "/api/metrics", httpOpts{}, func(t *testing.T, r *http.Response) {
111+
require.Equal(t, http.StatusOK, r.StatusCode)
112+
})
113+
}

pkg/server/api/server.go

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/pingcap/tiproxy/pkg/proxy/proxyprotocol"
2424
mgrrp "github.com/pingcap/tiproxy/pkg/sqlreplay/manager"
2525
"github.com/pingcap/tiproxy/pkg/util/waitgroup"
26+
"github.com/soheilhy/cmux"
2627
"go.uber.org/atomic"
2728
"go.uber.org/ratelimit"
2829
"go.uber.org/zap"
@@ -130,20 +131,47 @@ func NewServer(cfg config.API, lg *zap.Logger, mgr Managers, handler HTTPHandler
130131
}
131132

132133
if tlscfg := mgr.CertMgr.ServerHTTPTLS(); tlscfg != nil {
133-
h.listener = tls.NewListener(h.listener, tlscfg)
134+
mux := cmux.New(h.listener)
135+
mux.SetReadTimeout(DefConnTimeout)
136+
plainHealthListener := mux.Match(cmux.HTTP1Fast())
137+
tlsListener := tls.NewListener(mux.Match(cmux.TLS()), tlscfg)
138+
139+
h.serveHTTP("HTTP health", plainHealthListener, h.newHTTPHealthHandler())
140+
h.serveHTTP("HTTPS", tlsListener, engine.Handler())
141+
h.wg.RunWithRecover(func() {
142+
lg.Info("HTTP mux closed", zap.Error(mux.Serve()))
143+
}, nil, h.lg)
144+
return h, nil
134145
}
135146

147+
h.serveHTTP("HTTP", h.listener, engine.Handler())
148+
return h, nil
149+
}
150+
151+
func (h *Server) serveHTTP(name string, listener net.Listener, handler http.Handler) {
136152
hsrv := http.Server{
137-
Handler: engine.Handler(),
153+
Handler: handler,
138154
ReadHeaderTimeout: DefConnTimeout,
139155
IdleTimeout: DefConnTimeout,
140156
}
141157

142158
h.wg.RunWithRecover(func() {
143-
lg.Info("HTTP closed", zap.Error(hsrv.Serve(h.listener)))
159+
h.lg.Info(name+" closed", zap.Error(hsrv.Serve(listener)))
144160
}, nil, h.lg)
161+
}
145162

146-
return h, nil
163+
func (h *Server) newHTTPHealthHandler() http.Handler {
164+
engine := gin.New()
165+
engine.Use(
166+
gin.Recovery(),
167+
h.rateLimit,
168+
h.readyState,
169+
h.attachLogger,
170+
)
171+
// Keep the plaintext health routes consistent with the main server.
172+
engine.GET("/api/debug/health", h.DebugHealth)
173+
engine.GET("/debug/health", h.DebugHealth)
174+
return engine.Handler()
147175
}
148176

149177
func (h *Server) rateLimit(c *gin.Context) {

pkg/server/api/server_test.go

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package api
55

66
import (
77
"context"
8+
"crypto/tls"
89
"fmt"
910
"io"
1011
"net/http"
@@ -28,10 +29,18 @@ type httpOpts struct {
2829
type doHTTPFunc func(t *testing.T, method string, path string, opts httpOpts, f func(*testing.T, *http.Response))
2930

3031
func createServer(t *testing.T) (*Server, doHTTPFunc) {
32+
srv, doHTTP, _ := createServerWithConfig(t, "")
33+
return srv, doHTTP
34+
}
35+
36+
func createServerWithConfig(t *testing.T, tomlConfig string) (*Server, doHTTPFunc, doHTTPFunc) {
3137
lg, _ := logger.CreateLoggerForTest(t)
3238
ready := atomic.NewBool(true)
3339
cfgmgr := mgrcfg.NewConfigManager()
3440
require.NoError(t, cfgmgr.Init(context.Background(), "", ""))
41+
if tomlConfig != "" {
42+
require.NoError(t, cfgmgr.SetTOMLConfig([]byte(tomlConfig)))
43+
}
3544
crtmgr := mgrcrt.NewCertManager()
3645
require.NoError(t, crtmgr.Init(cfgmgr.GetConfig(), lg, cfgmgr.WatchConfig()))
3746
nsMgr := newMockNamespaceManager()
@@ -49,21 +58,31 @@ func createServer(t *testing.T) (*Server, doHTTPFunc) {
4958
require.NoError(t, srv.Close())
5059
})
5160

52-
addr := fmt.Sprintf("http://%s", srv.listener.Addr().String())
53-
return srv, func(t *testing.T, method, pa string, opts httpOpts, f func(*testing.T, *http.Response)) {
54-
if pa[0] != '/' {
55-
pa = "/" + pa
56-
}
57-
req, err := http.NewRequest(method, fmt.Sprintf("%s%s", addr, pa), opts.reader)
58-
require.NoError(t, err)
59-
for key, value := range opts.header {
60-
req.Header.Set(key, value)
61+
addr := srv.listener.Addr().String()
62+
httpsClient := &http.Client{
63+
Transport: &http.Transport{
64+
TLSClientConfig: &tls.Config{
65+
InsecureSkipVerify: true,
66+
},
67+
},
68+
}
69+
do := func(scheme string, client *http.Client) doHTTPFunc {
70+
return func(t *testing.T, method, pa string, opts httpOpts, f func(*testing.T, *http.Response)) {
71+
if pa[0] != '/' {
72+
pa = "/" + pa
73+
}
74+
req, err := http.NewRequest(method, fmt.Sprintf("%s://%s%s", scheme, addr, pa), opts.reader)
75+
require.NoError(t, err)
76+
for key, value := range opts.header {
77+
req.Header.Set(key, value)
78+
}
79+
resp, err := client.Do(req)
80+
require.NoError(t, err)
81+
f(t, resp)
82+
require.NoError(t, resp.Body.Close())
6183
}
62-
resp, err := http.DefaultClient.Do(req)
63-
require.NoError(t, err)
64-
f(t, resp)
65-
require.NoError(t, resp.Body.Close())
6684
}
85+
return srv, do("http", http.DefaultClient), do("https", httpsClient)
6786
}
6887

6988
func TestGrpc(t *testing.T) {

0 commit comments

Comments
 (0)