2019-05-22 23:16:55 +02:00
package httpserver
import (
"context"
"crypto/tls"
2023-11-13 20:13:50 +01:00
_ "embed"
2020-06-30 23:02:02 +02:00
"errors"
2019-05-22 23:16:55 +02:00
"flag"
"fmt"
"io"
2021-05-11 21:03:48 +02:00
"log"
2019-05-22 23:16:55 +02:00
"net"
"net/http"
"net/http/pprof"
2021-05-03 10:51:15 +02:00
"os"
2019-05-22 23:16:55 +02:00
"runtime"
"strconv"
"strings"
"sync"
2020-05-07 14:20:06 +02:00
"sync/atomic"
2019-05-22 23:16:55 +02:00
"time"
2022-07-21 18:49:52 +02:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/appmetrics"
2020-09-03 21:22:26 +02:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
2021-10-19 23:45:05 +02:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
2019-05-22 23:16:55 +02:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
"github.com/VictoriaMetrics/metrics"
2023-02-27 19:16:58 +01:00
"github.com/klauspost/compress/gzhttp"
2020-09-08 18:54:41 +02:00
"github.com/valyala/fastrand"
2019-05-22 23:16:55 +02:00
)
var (
2024-02-09 02:15:04 +01:00
tlsEnable = flagutil . NewArrayBool ( "tls" , "Whether to enable TLS for incoming HTTP requests at the given -httpListenAddr (aka https). -tlsCertFile and -tlsKeyFile must be set if -tls is set. " +
2024-02-06 16:40:48 +01:00
"See also -mtls" )
2024-02-09 02:15:04 +01:00
tlsCertFile = flagutil . NewArrayString ( "tlsCertFile" , "Path to file with TLS certificate for the corresponding -httpListenAddr if -tls is set. " +
"Prefer ECDSA certs instead of RSA certs as RSA certs are slower. The provided certificate file is automatically re-read every second, so it can be dynamically updated" )
tlsKeyFile = flagutil . NewArrayString ( "tlsKeyFile" , "Path to file with TLS key for the corresponding -httpListenAddr if -tls is set. " +
"The provided key file is automatically re-read every second, so it can be dynamically updated" )
2022-10-01 17:26:05 +02:00
tlsCipherSuites = flagutil . NewArrayString ( "tlsCipherSuites" , "Optional list of TLS cipher suites for incoming requests over HTTPS if -tls is set. See the list of supported cipher suites at https://pkg.go.dev/crypto/tls#pkg-constants" )
2024-02-09 02:15:04 +01:00
tlsMinVersion = flagutil . NewArrayString ( "tlsMinVersion" , "Optional minimum TLS version to use for the corresponding -httpListenAddr if -tls is set. " +
2022-09-26 16:35:45 +02:00
"Supported values: TLS10, TLS11, TLS12, TLS13" )
2019-05-22 23:16:55 +02:00
2020-05-02 12:07:30 +02:00
pathPrefix = flag . String ( "http.pathPrefix" , "" , "An optional prefix to add to all the paths handled by http server. For example, if '-http.pathPrefix=/foo/bar' is set, " +
"then all the http requests will be handled on '/foo/bar/*' paths. This may be useful for proxied requests. " +
"See https://www.robustperception.io/using-external-urls-and-proxies-with-prometheus" )
2023-07-07 13:50:13 +02:00
httpAuthUsername = flag . String ( "httpAuth.username" , "" , "Username for HTTP server's Basic Auth. The authentication is disabled if empty. See also -httpAuth.password" )
2024-01-21 20:58:26 +01:00
httpAuthPassword = flagutil . NewPassword ( "httpAuth.password" , "Password for HTTP server's Basic Auth. The authentication is disabled if -httpAuth.username is empty" )
metricsAuthKey = flagutil . NewPassword ( "metricsAuthKey" , "Auth key for /metrics endpoint. It must be passed via authKey query arg. It overrides httpAuth.* settings" )
flagsAuthKey = flagutil . NewPassword ( "flagsAuthKey" , "Auth key for /flags endpoint. It must be passed via authKey query arg. It overrides httpAuth.* settings" )
pprofAuthKey = flagutil . NewPassword ( "pprofAuthKey" , "Auth key for /debug/pprof/* endpoints. It must be passed via authKey query arg. It overrides httpAuth.* settings" )
2019-05-24 11:18:40 +02:00
2023-05-10 09:50:41 +02:00
disableResponseCompression = flag . Bool ( "http.disableResponseCompression" , false , "Disable compression of HTTP responses to save CPU resources. By default, compression is enabled to save network bandwidth" )
2021-04-12 11:28:04 +02:00
maxGracefulShutdownDuration = flag . Duration ( "http.maxGracefulShutdownDuration" , 7 * time . Second , ` The maximum duration for a graceful shutdown of the HTTP server. A highly loaded server may require increased value for a graceful shutdown ` )
2021-05-18 15:24:43 +02:00
shutdownDelay = flag . Duration ( "http.shutdownDelay" , 0 , ` Optional delay before http server shutdown. During this delay, the server returns non-OK responses from /health page, so load balancers can route new requests to other servers ` )
2021-04-12 11:28:04 +02:00
idleConnTimeout = flag . Duration ( "http.idleConnTimeout" , time . Minute , "Timeout for incoming idle http connections" )
2024-02-23 21:00:55 +01:00
connTimeout = flag . Duration ( "http.connTimeout" , 2 * time . Minute , "Incoming connections to -httpListenAddr are closed after the configured timeout. " +
"This may help evenly spreading load among a cluster of services behind TCP-level load balancer. Zero value disables closing of incoming connections" )
2023-10-30 11:33:38 +01:00
2024-02-23 21:00:55 +01:00
headerHSTS = flag . String ( "http.header.hsts" , "" , "Value for 'Strict-Transport-Security' header, recommended: 'max-age=31536000; includeSubDomains'" )
2023-10-30 11:33:38 +01:00
headerFrameOptions = flag . String ( "http.header.frameOptions" , "" , "Value for 'X-Frame-Options' header" )
2024-02-23 21:00:55 +01:00
headerCSP = flag . String ( "http.header.csp" , "" , ` Value for 'Content-Security-Policy' header, recommended: "default-src 'self'" ` )
2019-05-22 23:16:55 +02:00
)
var (
2020-05-07 14:20:06 +02:00
servers = make ( map [ string ] * server )
2019-05-22 23:16:55 +02:00
serversLock sync . Mutex
)
2020-05-07 14:20:06 +02:00
type server struct {
shutdownDelayDeadline int64
s * http . Server
}
2019-05-22 23:16:55 +02:00
// RequestHandler must serve the given request r and write response to w.
//
// RequestHandler must return true if the request has been served (successfully or not).
//
// RequestHandler must return false if it cannot serve the given request.
// In such cases the caller must serve the request.
type RequestHandler func ( w http . ResponseWriter , r * http . Request ) bool
2024-02-09 02:15:04 +01:00
// Serve starts an http server on the given addrs with the given optional rh.
2019-05-22 23:16:55 +02:00
//
2023-04-27 13:02:47 +02:00
// By default all the responses are transparently compressed, since egress traffic is usually expensive.
2019-05-24 11:18:40 +02:00
//
2024-02-09 02:15:04 +01:00
// The compression can be disabled by specifying -http.disableResponseCompression command-line flag.
2023-01-27 08:08:35 +01:00
//
2024-02-09 02:15:04 +01:00
// If useProxyProtocol is set to true for the corresponding addr, then the incoming connections are accepted via proxy protocol.
2023-01-27 08:08:35 +01:00
// See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
2024-02-09 02:15:04 +01:00
func Serve ( addrs [ ] string , useProxyProtocol * flagutil . ArrayBool , rh RequestHandler ) {
2021-12-02 10:55:58 +01:00
if rh == nil {
rh = func ( w http . ResponseWriter , r * http . Request ) bool {
return false
}
}
2024-02-09 02:15:04 +01:00
for idx , addr := range addrs {
if addr == "" {
continue
}
useProxyProto := false
if useProxyProtocol != nil {
useProxyProto = useProxyProtocol . GetOptionalArg ( idx )
}
go serve ( addr , useProxyProto , rh , idx )
}
}
func serve ( addr string , useProxyProtocol bool , rh RequestHandler , idx int ) {
2019-05-22 23:16:55 +02:00
scheme := "http"
2024-02-09 02:15:04 +01:00
if tlsEnable . GetOptionalArg ( idx ) {
2019-05-22 23:16:55 +02:00
scheme = "https"
}
2021-12-08 15:40:15 +01:00
hostAddr := addr
if strings . HasPrefix ( hostAddr , ":" ) {
hostAddr = "127.0.0.1" + hostAddr
}
2024-02-09 02:15:04 +01:00
logger . Infof ( "starting server at %s://%s/" , scheme , hostAddr )
2021-12-08 15:40:15 +01:00
logger . Infof ( "pprof handlers are exposed at %s://%s/debug/pprof/" , scheme , hostAddr )
2022-04-16 14:51:34 +02:00
var tlsConfig * tls . Config
2024-02-09 02:15:04 +01:00
if tlsEnable . GetOptionalArg ( idx ) {
certFile := tlsCertFile . GetOptionalArg ( idx )
keyFile := tlsKeyFile . GetOptionalArg ( idx )
minVersion := tlsMinVersion . GetOptionalArg ( idx )
tc , err := netutil . GetServerTLSConfig ( certFile , keyFile , minVersion , * tlsCipherSuites )
2019-05-22 23:16:55 +02:00
if err != nil {
2024-02-09 02:15:04 +01:00
logger . Fatalf ( "cannot load TLS cert from -tlsCertFile=%q, -tlsKeyFile=%q, -tlsMinVersion=%q, -tlsCipherSuites=%q: %s" , certFile , keyFile , minVersion , * tlsCipherSuites , err )
2019-05-22 23:16:55 +02:00
}
2022-04-16 14:51:34 +02:00
tlsConfig = tc
}
2023-01-27 08:08:35 +01:00
ln , err := netutil . NewTCPListener ( scheme , addr , useProxyProtocol , tlsConfig )
2022-04-16 14:51:34 +02:00
if err != nil {
logger . Fatalf ( "cannot start http server at %s: %s" , addr , err )
2019-05-22 23:16:55 +02:00
}
serveWithListener ( addr , ln , rh )
}
func serveWithListener ( addr string , ln net . Listener , rh RequestHandler ) {
2020-05-07 14:20:06 +02:00
var s server
s . s = & http . Server {
Handler : gzipHandler ( & s , rh ) ,
2019-05-22 23:16:55 +02:00
2020-05-07 13:10:40 +02:00
// Disable http/2, since it doesn't give any advantages for VictoriaMetrics services.
2019-05-22 23:16:55 +02:00
TLSNextProto : make ( map [ string ] func ( * http . Server , * tls . Conn , http . Handler ) ) ,
2020-05-07 13:10:40 +02:00
ReadHeaderTimeout : 5 * time . Second ,
2020-09-01 14:32:50 +02:00
IdleTimeout : * idleConnTimeout ,
2019-05-22 23:16:55 +02:00
2020-05-07 13:10:40 +02:00
// Do not set ReadTimeout and WriteTimeout here,
// since these timeouts must be controlled by request handlers.
2019-05-22 23:16:55 +02:00
ErrorLog : logger . StdErrorLogger ( ) ,
2024-02-08 20:01:20 +01:00
}
if * connTimeout > 0 {
s . s . ConnContext = func ( ctx context . Context , c net . Conn ) context . Context {
2020-09-08 18:54:41 +02:00
timeoutSec := connTimeout . Seconds ( )
// Add a jitter for connection timeout in order to prevent Thundering herd problem
// when all the connections are established at the same time.
// See https://en.wikipedia.org/wiki/Thundering_herd_problem
jitterSec := fastrand . Uint32n ( uint32 ( timeoutSec / 10 ) )
deadline := fasttime . UnixTimestamp ( ) + uint64 ( timeoutSec ) + uint64 ( jitterSec )
return context . WithValue ( ctx , connDeadlineTimeKey , & deadline )
2024-02-08 20:01:20 +01:00
}
2019-05-22 23:16:55 +02:00
}
2024-02-08 20:01:20 +01:00
2019-05-22 23:16:55 +02:00
serversLock . Lock ( )
2020-05-07 14:20:06 +02:00
servers [ addr ] = & s
2019-05-22 23:16:55 +02:00
serversLock . Unlock ( )
2020-05-07 14:20:06 +02:00
if err := s . s . Serve ( ln ) ; err != nil {
2019-05-22 23:16:55 +02:00
if err == http . ErrServerClosed {
// The server gracefully closed.
return
}
logger . Panicf ( "FATAL: cannot serve http at %s: %s" , addr , err )
}
}
2020-09-03 21:22:26 +02:00
func whetherToCloseConn ( r * http . Request ) bool {
2024-02-08 20:01:20 +01:00
if * connTimeout <= 0 {
return false
}
2020-09-03 21:22:26 +02:00
ctx := r . Context ( )
2020-09-08 18:54:41 +02:00
v := ctx . Value ( connDeadlineTimeKey )
deadline , ok := v . ( * uint64 )
return ok && fasttime . UnixTimestamp ( ) > * deadline
2020-09-03 21:22:26 +02:00
}
2020-09-08 18:54:41 +02:00
var connDeadlineTimeKey = interface { } ( "connDeadlineSecs" )
2020-09-03 21:22:26 +02:00
2024-02-09 02:15:04 +01:00
// Stop stops the http server on the given addrs, which has been started via Serve func.
func Stop ( addrs [ ] string ) error {
var errGlobalLock sync . Mutex
var errGlobal error
var wg sync . WaitGroup
for _ , addr := range addrs {
if addr == "" {
continue
}
wg . Add ( 1 )
go func ( addr string ) {
if err := stop ( addr ) ; err != nil {
errGlobalLock . Lock ( )
errGlobal = err
errGlobalLock . Unlock ( )
}
wg . Done ( )
} ( addr )
}
wg . Wait ( )
return errGlobal
}
func stop ( addr string ) error {
2019-05-22 23:16:55 +02:00
serversLock . Lock ( )
s := servers [ addr ]
delete ( servers , addr )
serversLock . Unlock ( )
if s == nil {
2024-02-09 02:15:04 +01:00
err := fmt . Errorf ( "BUG: there is no server at %q" , addr )
2021-05-20 17:27:10 +02:00
logger . Panicf ( "%s" , err )
// The return is needed for golangci-lint: SA5011(related information): this check suggests that the pointer can be nil
return err
2019-05-22 23:16:55 +02:00
}
2020-05-07 14:20:06 +02:00
deadline := time . Now ( ) . Add ( * shutdownDelay ) . UnixNano ( )
atomic . StoreInt64 ( & s . shutdownDelayDeadline , deadline )
if * shutdownDelay > 0 {
// Sleep for a while until load balancer in front of the server
// notifies that "/health" endpoint returns non-OK responses.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/463 .
logger . Infof ( "Waiting for %.3fs before shutdown of http server %q, so load balancers could re-route requests to other servers" , shutdownDelay . Seconds ( ) , addr )
time . Sleep ( * shutdownDelay )
logger . Infof ( "Starting shutdown for http server %q" , addr )
}
ctx , cancel := context . WithTimeout ( context . Background ( ) , * maxGracefulShutdownDuration )
defer cancel ( )
if err := s . s . Shutdown ( ctx ) ; err != nil {
2020-04-30 00:36:56 +02:00
return fmt . Errorf ( "cannot gracefully shutdown http server at %q in %.3fs; " +
"probably, `-http.maxGracefulShutdownDuration` command-line flag value must be increased; error: %s" , addr , maxGracefulShutdownDuration . Seconds ( ) , err )
2019-05-22 23:16:55 +02:00
}
return nil
}
2020-05-07 14:20:06 +02:00
func gzipHandler ( s * server , rh RequestHandler ) http . HandlerFunc {
2023-02-27 19:16:58 +01:00
h := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2020-05-07 14:20:06 +02:00
handlerWrapper ( s , w , r , rh )
2023-02-27 19:16:58 +01:00
} )
if * disableResponseCompression {
return h
2019-05-22 23:16:55 +02:00
}
2023-02-27 19:16:58 +01:00
return gzipHandlerWrapper ( h )
2019-05-22 23:16:55 +02:00
}
2023-02-27 19:16:58 +01:00
var gzipHandlerWrapper = func ( ) func ( http . Handler ) http . HandlerFunc {
hw , err := gzhttp . NewWrapper ( gzhttp . CompressionLevel ( 1 ) )
if err != nil {
2023-10-25 21:24:01 +02:00
panic ( fmt . Errorf ( "BUG: cannot initialize gzip http wrapper: %w" , err ) )
2023-02-27 19:16:58 +01:00
}
return hw
} ( )
2024-01-21 01:12:51 +01:00
var (
metricsHandlerDuration = metrics . NewHistogram ( ` vm_http_request_duration_seconds { path="/metrics"} ` )
connTimeoutClosedConns = metrics . NewCounter ( ` vm_http_conn_timeout_closed_conns_total ` )
)
2019-05-22 23:16:55 +02:00
2021-05-11 21:03:48 +02:00
var hostname = func ( ) string {
h , err := os . Hostname ( )
if err != nil {
// Cannot use logger.Errorf, since it isn't initialized yet.
// So use log.Printf instead.
log . Printf ( "ERROR: cannot determine hostname: %s" , err )
return "unknown"
}
return h
} ( )
2020-05-07 14:20:06 +02:00
func handlerWrapper ( s * server , w http . ResponseWriter , r * http . Request , rh RequestHandler ) {
2021-05-03 10:51:15 +02:00
// 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.
// The following recover() code works around this by explicitly stopping the process after logging the panic.
// See https://github.com/golang/go/issues/16542#issuecomment-246549902 for details.
defer func ( ) {
2021-06-11 11:50:22 +02:00
if err := recover ( ) ; err != nil {
2021-05-03 10:51:15 +02:00
buf := make ( [ ] byte , 1 << 20 )
n := runtime . Stack ( buf , false )
fmt . Fprintf ( os . Stderr , "panic: %v\n\n%s" , err , buf [ : n ] )
os . Exit ( 1 )
}
} ( )
2023-10-31 18:33:40 +01:00
h := w . Header ( )
2023-10-30 11:33:38 +01:00
if * headerHSTS != "" {
2023-10-31 18:33:40 +01:00
h . Add ( "Strict-Transport-Security" , * headerHSTS )
2023-10-30 11:33:38 +01:00
}
if * headerFrameOptions != "" {
2023-10-31 18:33:40 +01:00
h . Add ( "X-Frame-Options" , * headerFrameOptions )
2023-10-30 11:33:38 +01:00
}
if * headerCSP != "" {
2023-10-31 18:33:40 +01:00
h . Add ( "Content-Security-Policy" , * headerCSP )
2023-10-30 11:33:38 +01:00
}
2023-10-31 18:33:40 +01:00
h . Add ( "X-Server-Hostname" , hostname )
2019-05-22 23:16:55 +02:00
requestsTotal . Inc ( )
2020-09-03 21:22:26 +02:00
if whetherToCloseConn ( r ) {
connTimeoutClosedConns . Inc ( )
2023-10-31 18:33:40 +01:00
h . Set ( "Connection" , "close" )
2020-09-03 21:22:26 +02:00
}
2022-08-02 11:59:03 +02:00
path := r . URL . Path
2023-11-13 20:13:50 +01:00
if strings . HasSuffix ( path , "/favicon.ico" ) {
w . Header ( ) . Set ( "Cache-Control" , "max-age=3600" )
faviconRequests . Inc ( )
w . Write ( faviconData )
return
}
2022-08-02 11:59:03 +02:00
prefix := GetPathPrefix ( )
if prefix != "" {
// Trim -http.pathPrefix from path
prefixNoTrailingSlash := strings . TrimSuffix ( prefix , "/" )
if path == prefixNoTrailingSlash {
// Redirect to url with / at the end.
// This is needed for proper handling of relative urls in web browsers.
// Intentionally ignore query args, since it is expected that the requested url
// is composed by a human, so it doesn't contain query args.
2022-10-01 15:53:33 +02:00
Redirect ( w , prefix )
2022-08-02 11:59:03 +02:00
return
}
if ! strings . HasPrefix ( path , prefix ) {
Errorf ( w , r , "missing -http.pathPrefix=%q in the requested path %q" , * pathPrefix , path )
unsupportedRequestErrors . Inc ( )
return
}
path = path [ len ( prefix ) - 1 : ]
r . URL . Path = path
2020-05-02 12:07:30 +02:00
}
2019-05-22 23:16:55 +02:00
switch r . URL . Path {
case "/health" :
2023-10-31 18:33:40 +01:00
h . Set ( "Content-Type" , "text/plain; charset=utf-8" )
2020-05-07 14:20:06 +02:00
deadline := atomic . LoadInt64 ( & s . shutdownDelayDeadline )
if deadline <= 0 {
w . Write ( [ ] byte ( "OK" ) )
return
}
// 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.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/463 .
d := time . Until ( time . Unix ( 0 , deadline ) )
if d < 0 {
d = 0
}
errMsg := fmt . Sprintf ( "The server is in delayed shutdown mode, which will end in %.3fs" , d . Seconds ( ) )
http . Error ( w , errMsg , http . StatusServiceUnavailable )
2019-05-22 23:16:55 +02:00
return
2019-12-04 18:15:49 +01:00
case "/ping" :
2021-09-13 16:04:28 +02:00
// This is needed for compatibility with InfluxDB agents.
2019-12-04 18:15:49 +01:00
// See https://docs.influxdata.com/influxdb/v1.7/tools/api/#ping-http-endpoint
status := http . StatusNoContent
if verbose := r . FormValue ( "verbose" ) ; verbose == "true" {
status = http . StatusOK
}
w . WriteHeader ( status )
return
2019-05-22 23:16:55 +02:00
case "/metrics" :
metricsRequests . Inc ( )
2024-01-21 20:58:26 +01:00
if ! CheckAuthFlag ( w , r , metricsAuthKey . Get ( ) , "metricsAuthKey" ) {
2019-12-18 22:06:25 +01:00
return
}
startTime := time . Now ( )
2023-10-31 18:33:40 +01:00
h . Set ( "Content-Type" , "text/plain; charset=utf-8" )
2022-07-21 18:49:52 +02:00
appmetrics . WritePrometheusMetrics ( w )
2019-05-22 23:16:55 +02:00
metricsHandlerDuration . UpdateDuration ( startTime )
return
2021-10-19 23:45:05 +02:00
case "/flags" :
2024-01-21 20:58:26 +01:00
if ! CheckAuthFlag ( w , r , flagsAuthKey . Get ( ) , "flagsAuthKey" ) {
2022-06-20 16:09:32 +02:00
return
}
2023-10-31 18:33:40 +01:00
h . Set ( "Content-Type" , "text/plain; charset=utf-8" )
2021-10-19 23:45:05 +02:00
flagutil . WriteFlags ( w )
return
2021-12-02 13:36:56 +01:00
case "/-/healthy" :
// This is needed for Prometheus compatibility
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1833
fmt . Fprintf ( w , "VictoriaMetrics is Healthy.\n" )
return
case "/-/ready" :
// This is needed for Prometheus compatibility
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1833
fmt . Fprintf ( w , "VictoriaMetrics is Ready.\n" )
return
2023-04-18 14:47:26 +02:00
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
2019-05-22 23:16:55 +02:00
default :
if strings . HasPrefix ( r . URL . Path , "/debug/pprof/" ) {
pprofRequests . Inc ( )
2024-01-21 20:58:26 +01:00
if ! CheckAuthFlag ( w , r , pprofAuthKey . Get ( ) , "pprofAuthKey" ) {
2019-12-18 22:06:25 +01:00
return
}
2019-05-22 23:16:55 +02:00
pprofHandler ( r . URL . Path [ len ( "/debug/pprof/" ) : ] , w , r )
return
}
2023-01-11 00:46:13 +01:00
if ! CheckBasicAuth ( w , r ) {
2019-12-18 22:06:25 +01:00
return
}
2024-01-21 01:12:51 +01:00
w = & responseWriterWithAbort {
ResponseWriter : w ,
}
2019-05-22 23:16:55 +02:00
if rh ( w , r ) {
return
}
2020-07-20 13:00:33 +02:00
Errorf ( w , r , "unsupported path requested: %q" , r . URL . Path )
2019-05-22 23:16:55 +02:00
unsupportedRequestErrors . Inc ( )
return
}
}
2023-01-11 00:46:13 +01:00
// CheckAuthFlag checks whether the given authKey is set and valid
//
// Falls back to checkBasicAuth if authKey is not set
2023-01-11 00:51:55 +01:00
func CheckAuthFlag ( w http . ResponseWriter , r * http . Request , flagValue string , flagName string ) bool {
if flagValue == "" {
2023-01-11 00:46:13 +01:00
return CheckBasicAuth ( w , r )
}
2023-01-11 00:51:55 +01:00
if r . FormValue ( "authKey" ) != flagValue {
2023-10-31 12:48:02 +01:00
authKeyRequestErrors . Inc ( )
2023-01-11 00:46:13 +01:00
http . Error ( w , fmt . Sprintf ( "The provided authKey doesn't match -%s" , flagName ) , http . StatusUnauthorized )
return false
}
return true
}
// CheckBasicAuth validates credentials provided in request if httpAuth.* flags are set
// returns true if credentials are valid or httpAuth.* flags are not set
func CheckBasicAuth ( w http . ResponseWriter , r * http . Request ) bool {
2019-05-22 23:16:55 +02:00
if len ( * httpAuthUsername ) == 0 {
// HTTP Basic Auth is disabled.
return true
}
username , password , ok := r . BasicAuth ( )
2023-10-31 12:48:02 +01:00
if ok {
2024-01-21 20:58:26 +01:00
if username == * httpAuthUsername && password == httpAuthPassword . Get ( ) {
2023-10-31 12:48:02 +01:00
return true
}
authBasicRequestErrors . Inc ( )
2019-05-22 23:16:55 +02:00
}
2023-10-31 12:48:02 +01:00
2019-05-22 23:16:55 +02:00
w . Header ( ) . Set ( "WWW-Authenticate" , ` Basic realm="VictoriaMetrics" ` )
http . Error ( w , "" , http . StatusUnauthorized )
return false
}
// EnableCORS enables https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
// on the response.
func EnableCORS ( w http . ResponseWriter , _ * http . Request ) {
w . Header ( ) . Set ( "Access-Control-Allow-Origin" , "*" )
}
func pprofHandler ( profileName string , w http . ResponseWriter , r * http . Request ) {
// This switch has been stolen from init func at https://golang.org/src/net/http/pprof/pprof.go
switch profileName {
case "cmdline" :
pprofCmdlineRequests . Inc ( )
pprof . Cmdline ( w , r )
case "profile" :
pprofProfileRequests . Inc ( )
pprof . Profile ( w , r )
case "symbol" :
pprofSymbolRequests . Inc ( )
pprof . Symbol ( w , r )
case "trace" :
pprofTraceRequests . Inc ( )
pprof . Trace ( w , r )
case "mutex" :
pprofMutexRequests . Inc ( )
seconds , _ := strconv . Atoi ( r . FormValue ( "seconds" ) )
if seconds <= 0 {
seconds = 10
}
prev := runtime . SetMutexProfileFraction ( 10 )
time . Sleep ( time . Duration ( seconds ) * time . Second )
pprof . Index ( w , r )
runtime . SetMutexProfileFraction ( prev )
default :
pprofDefaultRequests . Inc ( )
pprof . Index ( w , r )
}
}
var (
metricsRequests = metrics . NewCounter ( ` vm_http_requests_total { path="/metrics"} ` )
pprofRequests = metrics . NewCounter ( ` vm_http_requests_total { path="/debug/pprof/"} ` )
pprofCmdlineRequests = metrics . NewCounter ( ` vm_http_requests_total { path="/debug/pprof/cmdline"} ` )
pprofProfileRequests = metrics . NewCounter ( ` vm_http_requests_total { path="/debug/pprof/profile"} ` )
pprofSymbolRequests = metrics . NewCounter ( ` vm_http_requests_total { path="/debug/pprof/symbol"} ` )
pprofTraceRequests = metrics . NewCounter ( ` vm_http_requests_total { path="/debug/pprof/trace"} ` )
pprofMutexRequests = metrics . NewCounter ( ` vm_http_requests_total { path="/debug/pprof/mutex"} ` )
pprofDefaultRequests = metrics . NewCounter ( ` vm_http_requests_total { path="/debug/pprof/default"} ` )
2023-11-13 20:13:50 +01:00
faviconRequests = metrics . NewCounter ( ` vm_http_requests_total { path="*/favicon.ico"} ` )
2019-05-22 23:16:55 +02:00
2023-10-31 18:52:37 +01:00
authBasicRequestErrors = metrics . NewCounter ( ` vm_http_request_errors_total { path="*", reason="wrong_basic_auth"} ` )
authKeyRequestErrors = metrics . NewCounter ( ` vm_http_request_errors_total { path="*", reason="wrong_auth_key"} ` )
2023-04-27 13:02:47 +02:00
unsupportedRequestErrors = metrics . NewCounter ( ` vm_http_request_errors_total { path="*", reason="unsupported"} ` )
2019-05-22 23:16:55 +02:00
requestsTotal = metrics . NewCounter ( ` vm_http_requests_all_total ` )
)
2023-11-13 20:13:50 +01:00
//go:embed favicon.ico
var faviconData [ ] byte
2020-07-31 17:00:21 +02:00
// GetQuotedRemoteAddr returns quoted remote address.
func GetQuotedRemoteAddr ( r * http . Request ) string {
2023-08-11 14:19:44 +02:00
remoteAddr := r . RemoteAddr
2020-07-29 12:12:15 +02:00
if addr := r . Header . Get ( "X-Forwarded-For" ) ; addr != "" {
2023-08-11 14:19:44 +02:00
remoteAddr += ", X-Forwarded-For: " + addr
2020-07-29 12:12:15 +02:00
}
2023-08-11 14:19:44 +02:00
// quote remoteAddr and X-Forwarded-For, since they may contain untrusted input
return strconv . Quote ( remoteAddr )
2020-07-31 17:00:21 +02:00
}
2024-01-21 01:12:51 +01:00
type responseWriterWithAbort struct {
http . ResponseWriter
sentHeaders bool
aborted bool
}
func ( rwa * responseWriterWithAbort ) Write ( data [ ] byte ) ( int , error ) {
if rwa . aborted {
return 0 , fmt . Errorf ( "response connection is aborted" )
}
if ! rwa . sentHeaders {
rwa . sentHeaders = true
}
return rwa . ResponseWriter . Write ( data )
}
func ( rwa * responseWriterWithAbort ) WriteHeader ( statusCode int ) {
if rwa . aborted {
logger . WarnfSkipframes ( 1 , "cannot write response headers with statusCode=%d, since the response connection has been aborted" , statusCode )
return
}
if rwa . sentHeaders {
logger . WarnfSkipframes ( 1 , "cannot write response headers with statusCode=%d, since they were already sent" , statusCode )
return
}
rwa . ResponseWriter . WriteHeader ( statusCode )
rwa . sentHeaders = true
}
// abort aborts the client connection associated with rwa.
//
// The last http chunk in the response stream is intentionally written incorrectly,
// so the client, which reads the response, could notice this error.
func ( rwa * responseWriterWithAbort ) abort ( ) {
if ! rwa . sentHeaders {
logger . Panicf ( "BUG: abort can be called only after http response headers are sent" )
}
if rwa . aborted {
logger . WarnfSkipframes ( 2 , "cannot abort the connection, since it has been already aborted" )
return
}
hj , ok := rwa . ResponseWriter . ( http . Hijacker )
if ! ok {
logger . Panicf ( "BUG: ResponseWriter must implement http.Hijacker interface" )
}
conn , bw , err := hj . Hijack ( )
if err != nil {
logger . WarnfSkipframes ( 2 , "cannot hijack response connection: %s" , err )
return
}
// Just write an error message into the client connection as is without http chunked encoding.
// This is needed in order to notify the client about the aborted connection.
_ , _ = bw . WriteString ( "\nthe connection has been aborted; see the last line in the response and/or in the server log for the reason\n" )
_ = bw . Flush ( )
// Forcibly close the client connection in order to break http keep-alive at client side.
_ = conn . Close ( )
rwa . aborted = true
}
2020-07-31 17:00:21 +02:00
// Errorf writes formatted error message to w and to logger.
func Errorf ( w http . ResponseWriter , r * http . Request , format string , args ... interface { } ) {
errStr := fmt . Sprintf ( format , args ... )
remoteAddr := GetQuotedRemoteAddr ( r )
2021-07-07 11:59:03 +02:00
requestURI := GetRequestURI ( r )
errStr = fmt . Sprintf ( "remoteAddr: %s; requestURI: %s; %s" , remoteAddr , requestURI , errStr )
2020-04-15 19:50:12 +02:00
logger . WarnfSkipframes ( 1 , "%s" , errStr )
2019-08-23 08:46:45 +02:00
// Extract statusCode from args
statusCode := http . StatusBadRequest
2020-06-30 23:02:02 +02:00
var esc * ErrorWithStatusCode
2019-08-23 08:46:45 +02:00
for _ , arg := range args {
2020-06-30 23:02:02 +02:00
if err , ok := arg . ( error ) ; ok && errors . As ( err , & esc ) {
2019-08-23 08:46:45 +02:00
statusCode = esc . StatusCode
break
}
}
2024-01-21 01:12:51 +01:00
if rwa , ok := w . ( * responseWriterWithAbort ) ; ok && rwa . sentHeaders {
// HTTP status code has been already sent to client, so it cannot be sent again.
// Just write errStr to the response and abort the client connection, so the client could notice the error.
fmt . Fprintf ( w , "\n%s\n" , errStr )
rwa . abort ( )
return
}
2019-08-23 08:46:45 +02:00
http . Error ( w , errStr , statusCode )
}
// ErrorWithStatusCode is error with HTTP status code.
//
// The given StatusCode is sent to client when the error is passed to Errorf.
type ErrorWithStatusCode struct {
Err error
StatusCode int
}
2020-06-30 23:53:43 +02:00
// Unwrap returns e.Err.
//
// This is used by standard errors package. See https://golang.org/pkg/errors
func ( e * ErrorWithStatusCode ) Unwrap ( ) error {
return e . Err
}
2019-08-23 08:46:45 +02:00
// Error implements error interface.
func ( e * ErrorWithStatusCode ) Error ( ) string {
return e . Err . Error ( )
2019-05-22 23:16:55 +02:00
}
2024-02-09 02:15:04 +01:00
// IsTLS indicates is tls enabled or not for -httpListenAddr at the given idx.
func IsTLS ( idx int ) bool {
return tlsEnable . GetOptionalArg ( idx )
2020-04-01 21:29:11 +02:00
}
2020-12-14 12:36:48 +01:00
// GetPathPrefix - returns http server path prefix.
func GetPathPrefix ( ) string {
2022-08-02 11:59:03 +02:00
prefix := * pathPrefix
if prefix == "" {
return ""
}
if ! strings . HasPrefix ( prefix , "/" ) {
prefix = "/" + prefix
}
if ! strings . HasSuffix ( prefix , "/" ) {
prefix += "/"
}
return prefix
2020-12-14 12:36:48 +01:00
}
2021-04-30 08:19:08 +02:00
// WriteAPIHelp writes pathList to w in HTML format.
func WriteAPIHelp ( w io . Writer , pathList [ ] [ 2 ] string ) {
for _ , p := range pathList {
p , doc := p [ 0 ] , p [ 1 ]
fmt . Fprintf ( w , "<a href=%q>%s</a> - %s<br/>" , p , p , doc )
}
}
2021-07-07 11:59:03 +02:00
// GetRequestURI returns requestURI for r.
func GetRequestURI ( r * http . Request ) string {
requestURI := r . RequestURI
2023-02-23 03:58:44 +01:00
if r . Method != http . MethodPost {
2021-07-07 11:59:03 +02:00
return requestURI
}
_ = r . ParseForm ( )
queryArgs := r . PostForm . Encode ( )
if len ( queryArgs ) == 0 {
return requestURI
}
delimiter := "?"
if strings . Contains ( requestURI , delimiter ) {
delimiter = "&"
}
return requestURI + delimiter + queryArgs
}
2022-08-02 11:59:03 +02:00
2022-10-01 15:53:33 +02:00
// Redirect redirects to the given url.
func Redirect ( w http . ResponseWriter , url string ) {
2022-08-02 11:59:03 +02:00
// Do not use http.Redirect, since it breaks relative redirects
// if the http.Request.URL contains unexpected url.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2918
w . Header ( ) . Set ( "Location" , url )
2022-10-01 15:53:33 +02:00
// Use http.StatusFound instead of http.StatusMovedPermanently,
// since browsers can cache incorrect redirects returned with StatusMovedPermanently.
// This may require browser cache cleaning after the incorrect redirect is fixed.
w . WriteHeader ( http . StatusFound )
2022-08-02 11:59:03 +02:00
}
2023-01-24 07:22:05 +01:00
// LogError logs the errStr with the context from req.
func LogError ( req * http . Request , errStr string ) {
uri := GetRequestURI ( req )
remoteAddr := GetQuotedRemoteAddr ( req )
logger . Errorf ( "uri: %s, remote address: %q: %s" , uri , remoteAddr , errStr )
}