mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-02-19 15:30:17 +00:00
app/vmauth: allow to disable built-in routes
vmauth uses 'lib/httpserver' for serving HTTP requests. This server unconditionally defines built-in routes (such as '/metrics', '/health', etc). It makes impossible to proxy `HTTP` requests to backends with the same routes. Since vmauth's httpserver matches built-in route and return local response. This commit adds new flag `httpListenAddr.disableBuiltinRoutes` with default value of false, which allows to disable built-in routes per httpListenAddr. It's recommend to disable built-in routes for the main httpListenAddr and expose it at the additional httpListenAddr. For example given configuration disables built-in routes at `0.0.0.0:8427` address and serves it at `0.0.0.0:8426`: `./bin/vmauth --auth.config=config.yaml --httpListenAddr=:8427,:8426 --httpListenAddr.disableBuiltinRoutes=true,false` Related issues: - https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6468 - https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7345 Signed-off-by: f41gh7 <nik@victoriametrics.com>
This commit is contained in:
parent
be24fbe8ae
commit
691561ab6f
9 changed files with 327 additions and 58 deletions
app/vmauth
apptest
docs
lib/httpserver
|
@ -35,6 +35,9 @@ var (
|
|||
useProxyProtocol = flagutil.NewArrayBool("httpListenAddr.useProxyProtocol", "Whether to use proxy protocol for connections accepted at the corresponding -httpListenAddr . "+
|
||||
"See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt . "+
|
||||
"With enabled proxy protocol http server cannot serve regular /metrics endpoint. Use -pushmetrics.url for metrics pushing")
|
||||
disableBuiltinRoutes = flagutil.NewArrayBool("httpListenAddr.disableBuiltinRoutes", "Whether to disable built-in routes for the corresponding -httpListenAddr. "+
|
||||
"This option allows proper routing of requests to backends with the same built-in routes, such as /metrics. "+
|
||||
"It is recommended to disable built-in routes for the main -httpListenAddr and expose them via an additional -httpListenAddr.")
|
||||
maxIdleConnsPerBackend = flag.Int("maxIdleConnsPerBackend", 100, "The maximum number of idle connections vmauth can open per each backend host. "+
|
||||
"See also -maxConcurrentRequests")
|
||||
idleConnTimeout = flag.Duration("idleConnTimeout", 50*time.Second, "The timeout for HTTP keep-alive connections to backend services. "+
|
||||
|
@ -91,7 +94,12 @@ func main() {
|
|||
logger.Infof("starting vmauth at %q...", listenAddrs)
|
||||
startTime := time.Now()
|
||||
initAuthConfig()
|
||||
go httpserver.Serve(listenAddrs, useProxyProtocol, requestHandler)
|
||||
serveOpts := &httpserver.ServeOptions{
|
||||
UseProxyProtocol: useProxyProtocol,
|
||||
DisableBuiltinRoutes: disableBuiltinRoutes,
|
||||
}
|
||||
rhs := buildRequestHandlers(listenAddrs)
|
||||
go httpserver.ServeWithOpts(listenAddrs, rhs, serveOpts)
|
||||
logger.Infof("started vmauth in %.3f seconds", time.Since(startTime).Seconds())
|
||||
|
||||
pushmetrics.Init()
|
||||
|
@ -109,7 +117,19 @@ func main() {
|
|||
logger.Infof("successfully stopped vmauth in %.3f seconds", time.Since(startTime).Seconds())
|
||||
}
|
||||
|
||||
func requestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
func buildRequestHandlers(listenAddrs []string) []httpserver.RequestHandler {
|
||||
rhs := make([]httpserver.RequestHandler, len(listenAddrs))
|
||||
for idx := range listenAddrs {
|
||||
rh := requestHandlerWithPrivateRoutes
|
||||
if disableBuiltinRoutes.GetOptionalArg(idx) {
|
||||
rh = requestHandler
|
||||
}
|
||||
rhs[idx] = rh
|
||||
}
|
||||
return rhs
|
||||
}
|
||||
|
||||
func requestHandlerWithPrivateRoutes(w http.ResponseWriter, r *http.Request) bool {
|
||||
switch r.URL.Path {
|
||||
case "/-/reload":
|
||||
if !httpserver.CheckAuthFlag(w, r, reloadAuthKey) {
|
||||
|
@ -120,6 +140,10 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
|
|||
w.WriteHeader(http.StatusOK)
|
||||
return true
|
||||
}
|
||||
return requestHandler(w, r)
|
||||
}
|
||||
|
||||
func requestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
|
||||
ats := getAuthTokensFromRequest(r)
|
||||
if len(ats) == 0 {
|
||||
|
|
|
@ -52,7 +52,7 @@ func TestRequestHandler(t *testing.T) {
|
|||
r.Header.Set("Pass-Header", "abc")
|
||||
|
||||
w := &fakeResponseWriter{}
|
||||
if !requestHandler(w, r) {
|
||||
if !requestHandlerWithPrivateRoutes(w, r) {
|
||||
t.Fatalf("unexpected false is returned from requestHandler")
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ package apptest
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -134,6 +136,23 @@ func (c *vmcluster) ForceFlush(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// MustStartVmauth is a test helper function that starts an instance of
|
||||
// vmauth and fails the test if the app fails to start.
|
||||
func (tc *TestCase) MustStartVmauth(instance string, flags []string, configFileYAML string) *Vmauth {
|
||||
tc.t.Helper()
|
||||
|
||||
configFilePath := path.Join(tc.t.TempDir(), "config.yaml")
|
||||
if err := os.WriteFile(configFilePath, []byte(configFileYAML), os.ModePerm); err != nil {
|
||||
tc.t.Fatalf("cannot init vmauth: config file write failed: %s", err)
|
||||
}
|
||||
app, err := StartVmauth(instance, flags, tc.cli, configFilePath)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("Could not start %s: %v", instance, err)
|
||||
}
|
||||
tc.addApp(instance, app)
|
||||
return app
|
||||
}
|
||||
|
||||
// MustStartDefaultCluster starts a typical cluster configuration with default
|
||||
// flags.
|
||||
func (tc *TestCase) MustStartDefaultCluster() PrometheusWriteQuerier {
|
||||
|
|
77
apptest/tests/vmauth_routing_test.go
Normal file
77
apptest/tests/vmauth_routing_test.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
|
||||
)
|
||||
|
||||
func TestVMAuthRouting(t *testing.T) {
|
||||
tc := apptest.NewTestCase(t)
|
||||
defer tc.Stop()
|
||||
|
||||
var proxiedRequestsCount int
|
||||
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
proxiedRequestsCount++
|
||||
}))
|
||||
defer backend.Close()
|
||||
|
||||
authConfig := fmt.Sprintf(`
|
||||
unauthorized_user:
|
||||
url_prefix: %s
|
||||
`, backend.URL)
|
||||
|
||||
const (
|
||||
// it's not possible to use random ports
|
||||
// it makes test flaky
|
||||
listenPortNoBuiltin = "50128"
|
||||
listenPortWithBuiltin = "50127"
|
||||
)
|
||||
|
||||
vmauthFlags := []string{
|
||||
fmt.Sprintf("-httpListenAddr=127.0.0.1:%s,127.0.0.1:%s", listenPortWithBuiltin, listenPortNoBuiltin),
|
||||
"-httpListenAddr.disableBuiltinRoutes=false,true",
|
||||
"-flagsAuthKey=protected",
|
||||
}
|
||||
vmauth := tc.MustStartVmauth("vmauth",
|
||||
vmauthFlags,
|
||||
authConfig)
|
||||
|
||||
var hc http.Client
|
||||
makeGetRequestExpectCode := func(targetURL string, expectCode int) {
|
||||
t.Helper()
|
||||
req, err := http.NewRequest("GET", targetURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot build http.Request for target=%q: %s", targetURL, err)
|
||||
}
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected http request error: %s", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != expectCode {
|
||||
responseText, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("cannot read response body: %s", err)
|
||||
}
|
||||
t.Fatalf("unexpected http response code: %d, want: %d, response text: %s", resp.StatusCode, expectCode, responseText)
|
||||
}
|
||||
}
|
||||
// built-in http server must reject request, since it protected with authKey
|
||||
makeGetRequestExpectCode(fmt.Sprintf("http://127.0.0.1:%s/flags", listenPortWithBuiltin), http.StatusUnauthorized)
|
||||
makeGetRequestExpectCode(fmt.Sprintf("http://127.0.0.1:%s/flags", listenPortNoBuiltin), http.StatusOK)
|
||||
if proxiedRequestsCount != 1 {
|
||||
t.Fatalf("expected to have 1 proxied request, got: %d", proxiedRequestsCount)
|
||||
}
|
||||
// reload config and ensure that it no longer proxy requests to the backend
|
||||
vmauth.UpdateConfiguration(t, "")
|
||||
makeGetRequestExpectCode(fmt.Sprintf("http://127.0.0.1:%s/flags", listenPortWithBuiltin), http.StatusUnauthorized)
|
||||
if proxiedRequestsCount != 1 {
|
||||
t.Fatalf("expected to have 1 proxied request, got: %d", proxiedRequestsCount)
|
||||
}
|
||||
}
|
77
apptest/vmauth.go
Normal file
77
apptest/vmauth.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package apptest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var httpBuilitinListenAddrRE = regexp.MustCompile(`pprof handlers are exposed at http://(.*:\d{1,5})/debug/pprof/`)
|
||||
|
||||
// Vmauth holds the state of a vmauth app and provides vmauth-specific
|
||||
// functions.
|
||||
type Vmauth struct {
|
||||
*app
|
||||
*ServesMetrics
|
||||
|
||||
httpListenAddr string
|
||||
configFilePath string
|
||||
cli *Client
|
||||
}
|
||||
|
||||
// StartVmauth starts an instance of vmauth with the given flags. It also
|
||||
// sets the default flags and populates the app instance state with runtime
|
||||
// values extracted from the application log (such as httpListenAddr)
|
||||
func StartVmauth(instance string, flags []string, cli *Client, configFilePath string) (*Vmauth, error) {
|
||||
extractREs := []*regexp.Regexp{
|
||||
httpBuilitinListenAddrRE,
|
||||
}
|
||||
|
||||
app, stderrExtracts, err := startApp(instance, "../../bin/vmauth", flags, &appOptions{
|
||||
defaultFlags: map[string]string{
|
||||
"-httpListenAddr": "127.0.0.1:0",
|
||||
"-auth.config": configFilePath,
|
||||
},
|
||||
extractREs: extractREs,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Vmauth{
|
||||
app: app,
|
||||
ServesMetrics: &ServesMetrics{
|
||||
metricsURL: fmt.Sprintf("http://%s/metrics", stderrExtracts[0]),
|
||||
cli: cli,
|
||||
},
|
||||
httpListenAddr: stderrExtracts[0],
|
||||
configFilePath: configFilePath,
|
||||
cli: cli,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateConfiguration performs configuration file reload for app and waits for configuration apply
|
||||
//
|
||||
// Due to second prescision of config reload metric, config cannot be reloaded more than 1 time in a second
|
||||
func (app *Vmauth) UpdateConfiguration(t *testing.T, configFileYAML string) {
|
||||
t.Helper()
|
||||
ct := int(time.Now().Unix())
|
||||
if err := os.WriteFile(app.configFilePath, []byte(configFileYAML), os.ModePerm); err != nil {
|
||||
t.Fatalf("unexpected error at UpdateConfiguration, cannot write configFile content: %s", err)
|
||||
}
|
||||
if err := app.process.Signal(syscall.SIGHUP); err != nil {
|
||||
t.Fatalf("unexpected signal error: %s", err)
|
||||
}
|
||||
for range 10 {
|
||||
ts := app.GetIntMetric(t, "vmauth_config_last_reload_success_timestamp_seconds")
|
||||
if ts < ct {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Fatalf("timeout waiting for config reload success")
|
||||
}
|
|
@ -48,6 +48,7 @@ Released at 2025-01-14
|
|||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add `markdown` support for comments during data export. [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7828).
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent/) and [Single-node VictoriaMetrics](https://docs.victoriametrics.com/): added `min` and `max` metrics for Datadog Sketches API metrics, changed `_` metric name separator to `.` if metrics are not sanitized for consistency.
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent/) and [Single-node VictoriaMetrics](https://docs.victoriametrics.com/): add service discovery support for [Marathon](https://mesosphere.github.io/marathon/). See [these docs](https://docs.victoriametrics.com/sd_configs/#marathon_sd_configs) and [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6642).
|
||||
* FEATURE: [vmauth](https://docs.victoriametrics.com/vmauth/): add `httpListenAddr.disableBuiltinRoutes` cmd-line flag to disable serving default HTTP routes `/metrics`, `/flags`, etc. It allows properly route requests to backends with the same service routes as vmauth. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6468) and [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7345) for details.
|
||||
* FEATURE: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/): support `-maxIngestionRate` cmd-line flag to ratelimit samples/sec ingested. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7377) for details.
|
||||
* FEATURE: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/) and `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): improve query performance on systems with high number of CPU cores. See [this PR](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7416) for details.
|
||||
* FEATURE: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/) and `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): add command-line flag `-search.maxBinaryOpPushdownLabelValues` to allow using labels with more candidate values as push down filter in binary operation. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7243). Thanks to @tydhot for implementation.
|
||||
|
|
|
@ -1073,6 +1073,8 @@ It is recommended protecting the following endpoints with authKeys:
|
|||
* `/metrics` with `-metricsAuthKey` command-line flag, so unauthorized users couldn't access [vmauth metrics](#monitoring).
|
||||
* `/debug/pprof` with `-pprofAuthKey` command-line flag, so unauthorized users couldn't access [profiling information](#profiling).
|
||||
|
||||
As an alternative, it's possible to disable all built-in vmauth routes with command-line flag `-httpListenAddr.disableBuiltinRoutes=true`. This flag could have multiple values: `-httpListenAddr.disableBuiltinRoutes=true,false` for each corresponding `-httpListenAddr=0.0.0.0:8427,127.0.0.1:8426`.
|
||||
|
||||
`vmauth` also supports the ability to restrict access by IP - see [these docs](#ip-filters). See also [concurrency limiting docs](#concurrency-limiting).
|
||||
|
||||
## Automatic issuing of TLS certificates
|
||||
|
@ -1281,6 +1283,11 @@ See the docs at https://docs.victoriametrics.com/vmauth/ .
|
|||
TCP address to listen for incoming http requests. See also -tls and -httpListenAddr.useProxyProtocol
|
||||
Supports an array of values separated by comma or specified via multiple flags.
|
||||
Value can contain comma inside single-quoted or double-quoted string, {}, [] and () braces.
|
||||
-httpListenAddr.disableBuiltinRoutes array
|
||||
Whether to disable built-in routes for the corresponding -httpListenAddr.
|
||||
This option allows proper routing of requests to backends with the same built-in routes, such as /metrics.
|
||||
It is recommended to disable built-in routes for the main -httpListenAddr and expose them via an additional -httpListenAddr.
|
||||
Empty values are set to false.
|
||||
-httpListenAddr.useProxyProtocol array
|
||||
Whether to use proxy protocol for connections accepted at the corresponding -httpListenAddr . See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt . With enabled proxy protocol http server cannot serve regular /metrics endpoint. Use -pushmetrics.url for metrics pushing
|
||||
Supports array of values separated by comma or specified via multiple flags.
|
||||
|
|
|
@ -83,6 +83,19 @@ type server struct {
|
|||
// In such cases the caller must serve the request.
|
||||
type RequestHandler func(w http.ResponseWriter, r *http.Request) bool
|
||||
|
||||
// ServeOptions defiens optional parameters for http server
|
||||
type ServeOptions struct {
|
||||
// UseProxyProtocol if is set to true for the corresponding addr, then the incoming connections are accepted via proxy protocol.
|
||||
// See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
|
||||
UseProxyProtocol *flagutil.ArrayBool
|
||||
// DisableBuiltinRoutes if set to true for the corresponding addr, then the http will not serve built-in routes, such as:
|
||||
// /health, /debug/pprof and few others
|
||||
// In addition basic auth check and authKey checks will be disabled for the given addr
|
||||
//
|
||||
// Mostly required by http proxy servers, which peforms own authorization and requests routing
|
||||
DisableBuiltinRoutes *flagutil.ArrayBool
|
||||
}
|
||||
|
||||
// Serve starts an http server on the given addrs with the given optional rh.
|
||||
//
|
||||
// By default all the responses are transparently compressed, since egress traffic is usually expensive.
|
||||
|
@ -97,23 +110,54 @@ func Serve(addrs []string, useProxyProtocol *flagutil.ArrayBool, rh RequestHandl
|
|||
return false
|
||||
}
|
||||
}
|
||||
opts := ServeOptions{
|
||||
UseProxyProtocol: useProxyProtocol,
|
||||
}
|
||||
for idx, addr := range addrs {
|
||||
if addr == "" {
|
||||
continue
|
||||
}
|
||||
useProxyProto := false
|
||||
if useProxyProtocol != nil {
|
||||
useProxyProto = useProxyProtocol.GetOptionalArg(idx)
|
||||
}
|
||||
go serve(addr, useProxyProto, rh, idx)
|
||||
go serve(addr, rh, idx, &opts)
|
||||
}
|
||||
}
|
||||
|
||||
func serve(addr string, useProxyProtocol bool, rh RequestHandler, idx int) {
|
||||
// ServeWithOpts starts an http server on the given addrs with the given optional request handlers.
|
||||
//
|
||||
// By default all the responses are transparently compressed, since egress traffic is usually expensive.
|
||||
//
|
||||
// The compression can be disabled by specifying -http.disableResponseCompression command-line flag.
|
||||
func ServeWithOpts(addrs []string, rhs []RequestHandler, opts *ServeOptions) {
|
||||
if len(rhs) == 0 {
|
||||
rhs = []RequestHandler{
|
||||
func(_ http.ResponseWriter, _ *http.Request) bool {
|
||||
return false
|
||||
},
|
||||
}
|
||||
}
|
||||
for idx, addr := range addrs {
|
||||
if addr == "" {
|
||||
continue
|
||||
}
|
||||
rh := rhs[0]
|
||||
if idx < len(rhs) {
|
||||
rh = rhs[idx]
|
||||
}
|
||||
go serve(addr, rh, idx, opts)
|
||||
}
|
||||
}
|
||||
|
||||
func serve(addr string, rh RequestHandler, idx int, opts *ServeOptions) {
|
||||
scheme := "http"
|
||||
if tlsEnable.GetOptionalArg(idx) {
|
||||
scheme = "https"
|
||||
}
|
||||
useProxyProto, disableBuiltinRoutes := false, false
|
||||
if opts.UseProxyProtocol != nil {
|
||||
useProxyProto = opts.UseProxyProtocol.GetOptionalArg(idx)
|
||||
}
|
||||
if opts.DisableBuiltinRoutes != nil {
|
||||
disableBuiltinRoutes = opts.DisableBuiltinRoutes.GetOptionalArg(idx)
|
||||
}
|
||||
var tlsConfig *tls.Config
|
||||
if tlsEnable.GetOptionalArg(idx) {
|
||||
certFile := tlsCertFile.GetOptionalArg(idx)
|
||||
|
@ -125,20 +169,22 @@ func serve(addr string, useProxyProtocol bool, rh RequestHandler, idx int) {
|
|||
}
|
||||
tlsConfig = tc
|
||||
}
|
||||
ln, err := netutil.NewTCPListener(scheme, addr, useProxyProtocol, tlsConfig)
|
||||
ln, err := netutil.NewTCPListener(scheme, addr, useProxyProto, tlsConfig)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot start http server at %s: %s", addr, err)
|
||||
} else {
|
||||
logger.Infof("started server at %s://%s/", scheme, ln.Addr())
|
||||
}
|
||||
logger.Infof("started server at %s://%s/", scheme, ln.Addr())
|
||||
if !disableBuiltinRoutes {
|
||||
logger.Infof("pprof handlers are exposed at %s://%s/debug/pprof/", scheme, ln.Addr())
|
||||
}
|
||||
serveWithListener(addr, ln, rh)
|
||||
|
||||
serveWithListener(addr, ln, rh, disableBuiltinRoutes)
|
||||
}
|
||||
|
||||
func serveWithListener(addr string, ln net.Listener, rh RequestHandler) {
|
||||
func serveWithListener(addr string, ln net.Listener, rh RequestHandler, disableBuiltinRoutes bool) {
|
||||
var s server
|
||||
|
||||
s.s = &http.Server{
|
||||
Handler: gzipHandler(&s, rh),
|
||||
|
||||
// Disable http/2, since it doesn't give any advantages for VictoriaMetrics services.
|
||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
|
||||
|
@ -162,6 +208,19 @@ func serveWithListener(addr string, ln net.Listener, rh RequestHandler) {
|
|||
return context.WithValue(ctx, connDeadlineTimeKey, &deadline)
|
||||
}
|
||||
}
|
||||
rhw := rh
|
||||
if !disableBuiltinRoutes {
|
||||
rhw = func(w http.ResponseWriter, r *http.Request) bool {
|
||||
return builtinRoutesHandler(&s, r, w, rh)
|
||||
}
|
||||
}
|
||||
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handlerWrapper(w, r, rhw)
|
||||
})
|
||||
if !*disableResponseCompression {
|
||||
h = gzipHandlerWrapper(h)
|
||||
}
|
||||
s.s.Handler = h
|
||||
|
||||
serversLock.Lock()
|
||||
servers[addr] = &s
|
||||
|
@ -244,16 +303,6 @@ func stop(addr string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func gzipHandler(s *server, rh RequestHandler) http.HandlerFunc {
|
||||
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handlerWrapper(s, w, r, rh)
|
||||
})
|
||||
if *disableResponseCompression {
|
||||
return h
|
||||
}
|
||||
return gzipHandlerWrapper(h)
|
||||
}
|
||||
|
||||
var gzipHandlerWrapper = func() func(http.Handler) http.HandlerFunc {
|
||||
hw, err := gzhttp.NewWrapper(gzhttp.CompressionLevel(1))
|
||||
if err != nil {
|
||||
|
@ -278,7 +327,7 @@ var hostname = func() string {
|
|||
return h
|
||||
}()
|
||||
|
||||
func handlerWrapper(s *server, w http.ResponseWriter, r *http.Request, rh RequestHandler) {
|
||||
func handlerWrapper(w http.ResponseWriter, r *http.Request, rh RequestHandler) {
|
||||
// All the VictoriaMetrics code assumes that panic stops the process.
|
||||
// Unfortunately, the standard net/http.Server recovers from panics in request handlers,
|
||||
// so VictoriaMetrics state can become inconsistent after the recovered panic.
|
||||
|
@ -310,12 +359,7 @@ func handlerWrapper(s *server, w http.ResponseWriter, r *http.Request, rh Reques
|
|||
h.Set("Connection", "close")
|
||||
}
|
||||
path := r.URL.Path
|
||||
if strings.HasSuffix(path, "/favicon.ico") {
|
||||
w.Header().Set("Cache-Control", "max-age=3600")
|
||||
faviconRequests.Inc()
|
||||
w.Write(faviconData)
|
||||
return
|
||||
}
|
||||
|
||||
prefix := GetPathPrefix()
|
||||
if prefix != "" {
|
||||
// Trim -http.pathPrefix from path
|
||||
|
@ -336,13 +380,40 @@ func handlerWrapper(s *server, w http.ResponseWriter, r *http.Request, rh Reques
|
|||
path = path[len(prefix)-1:]
|
||||
r.URL.Path = path
|
||||
}
|
||||
|
||||
w = &responseWriterWithAbort{
|
||||
ResponseWriter: w,
|
||||
}
|
||||
if rh(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
Errorf(w, r, "unsupported path requested: %q", r.URL.Path)
|
||||
unsupportedRequestErrors.Inc()
|
||||
}
|
||||
|
||||
func builtinRoutesHandler(s *server, r *http.Request, w http.ResponseWriter, rh RequestHandler) bool {
|
||||
if !isProtectedByAuthFlag(r.URL.Path) && !CheckBasicAuth(w, r) {
|
||||
return true
|
||||
}
|
||||
|
||||
h := w.Header()
|
||||
|
||||
path := r.URL.Path
|
||||
if strings.HasSuffix(path, "/favicon.ico") {
|
||||
w.Header().Set("Cache-Control", "max-age=3600")
|
||||
faviconRequests.Inc()
|
||||
w.Write(faviconData)
|
||||
return true
|
||||
}
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/health":
|
||||
h.Set("Content-Type", "text/plain; charset=utf-8")
|
||||
deadline := s.shutdownDelayDeadline.Load()
|
||||
if deadline <= 0 {
|
||||
w.Write([]byte("OK"))
|
||||
return
|
||||
return true
|
||||
}
|
||||
// Return non-OK response during grace period before shutting down the server.
|
||||
// Load balancers must notify these responses and re-route new requests to other servers.
|
||||
|
@ -353,7 +424,7 @@ func handlerWrapper(s *server, w http.ResponseWriter, r *http.Request, rh Reques
|
|||
}
|
||||
errMsg := fmt.Sprintf("The server is in delayed shutdown mode, which will end in %.3fs", d.Seconds())
|
||||
http.Error(w, errMsg, http.StatusServiceUnavailable)
|
||||
return
|
||||
return true
|
||||
case "/ping":
|
||||
// This is needed for compatibility with InfluxDB agents.
|
||||
// See https://docs.influxdata.com/influxdb/v1.7/tools/api/#ping-http-endpoint
|
||||
|
@ -362,64 +433,54 @@ func handlerWrapper(s *server, w http.ResponseWriter, r *http.Request, rh Reques
|
|||
status = http.StatusOK
|
||||
}
|
||||
w.WriteHeader(status)
|
||||
return
|
||||
return true
|
||||
case "/metrics":
|
||||
metricsRequests.Inc()
|
||||
if !CheckAuthFlag(w, r, metricsAuthKey) {
|
||||
return
|
||||
return true
|
||||
}
|
||||
startTime := time.Now()
|
||||
h.Set("Content-Type", "text/plain; charset=utf-8")
|
||||
appmetrics.WritePrometheusMetrics(w)
|
||||
metricsHandlerDuration.UpdateDuration(startTime)
|
||||
return
|
||||
return true
|
||||
case "/flags":
|
||||
if !CheckAuthFlag(w, r, flagsAuthKey) {
|
||||
return
|
||||
return true
|
||||
}
|
||||
h.Set("Content-Type", "text/plain; charset=utf-8")
|
||||
flagutil.WriteFlags(w)
|
||||
return
|
||||
return true
|
||||
case "/-/healthy":
|
||||
// This is needed for Prometheus compatibility
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1833
|
||||
fmt.Fprintf(w, "VictoriaMetrics is Healthy.\n")
|
||||
return
|
||||
return true
|
||||
case "/-/ready":
|
||||
// This is needed for Prometheus compatibility
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1833
|
||||
fmt.Fprintf(w, "VictoriaMetrics is Ready.\n")
|
||||
return
|
||||
return true
|
||||
case "/robots.txt":
|
||||
// This prevents search engines from indexing contents
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4128
|
||||
fmt.Fprintf(w, "User-agent: *\nDisallow: /\n")
|
||||
return
|
||||
return true
|
||||
default:
|
||||
if strings.HasPrefix(r.URL.Path, "/debug/pprof/") {
|
||||
pprofRequests.Inc()
|
||||
if !CheckAuthFlag(w, r, pprofAuthKey) {
|
||||
return
|
||||
return true
|
||||
}
|
||||
pprofHandler(r.URL.Path[len("/debug/pprof/"):], w, r)
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
if !isProtectedByAuthFlag(r.URL.Path) && !CheckBasicAuth(w, r) {
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
w = &responseWriterWithAbort{
|
||||
ResponseWriter: w,
|
||||
}
|
||||
if rh(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
Errorf(w, r, "unsupported path requested: %q", r.URL.Path)
|
||||
unsupportedRequestErrors.Inc()
|
||||
return
|
||||
}
|
||||
return rh(w, r)
|
||||
}
|
||||
|
||||
func isProtectedByAuthFlag(path string) bool {
|
||||
|
|
|
@ -162,8 +162,11 @@ func TestHandlerWrapper(t *testing.T) {
|
|||
|
||||
srv := &server{s: &http.Server{}}
|
||||
w := &httptest.ResponseRecorder{}
|
||||
handlerWrapper(srv, w, req, func(_ http.ResponseWriter, _ *http.Request) bool {
|
||||
return true
|
||||
|
||||
handlerWrapper(w, req, func(w http.ResponseWriter, r *http.Request) bool {
|
||||
return builtinRoutesHandler(srv, r, w, func(_ http.ResponseWriter, _ *http.Request) bool {
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
h := w.Header()
|
||||
|
|
Loading…
Reference in a new issue