diff --git a/go.mod b/go.mod index 91ea1a3..5f2fd63 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/randlabs/go-metrics -go 1.17 +go 1.19 require ( - github.com/prometheus/client_golang v1.13.0 - github.com/prometheus/client_model v0.2.0 - github.com/randlabs/go-webserver v1.1.6 + github.com/prometheus/client_golang v1.14.0 + github.com/prometheus/client_model v0.3.0 + github.com/randlabs/go-webserver v1.2.0 github.com/randlabs/rundown-protection v1.0.5 google.golang.org/protobuf v1.28.1 ) @@ -14,15 +14,15 @@ require ( github.com/andybalholm/brotli v1.0.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/fasthttp/router v1.4.11 // indirect + github.com/fasthttp/router v1.4.13 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/klauspost/compress v1.15.9 // indirect + github.com/klauspost/compress v1.15.12 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.39.0 // indirect + github.com/valyala/fasthttp v1.41.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 // indirect + golang.org/x/sys v0.2.0 // indirect ) diff --git a/go.sum b/go.sum index 13ad177..c4cdc7d 100644 --- a/go.sum +++ b/go.sum @@ -54,13 +54,14 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fasthttp/router v1.4.11 h1:99BvgVxeS2oOZBHnKr/okpdPq1jkn8WvYA2trh/71LY= -github.com/fasthttp/router v1.4.11/go.mod h1:luEEYkGBSAmYyPaMeIUGNgqY+FdHHYDOK9Kivaw7aNo= +github.com/fasthttp/router v1.4.13 h1:42M7+7tNO6clb5seb4HhXlBIX1lnNv8DLhiT6jUv75A= +github.com/fasthttp/router v1.4.13/go.mod h1:mVhHMaSQA2Hi1HeuL/ZMuZpsZWk5bya75EpaDr3fO7E= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -142,9 +143,9 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= +github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -169,13 +170,14 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= @@ -189,8 +191,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/randlabs/go-webserver v1.1.6 h1:cTh6ihNgvV14wQAY3xX6JMsmHEMR9ZHkKG+BODnVnpE= -github.com/randlabs/go-webserver v1.1.6/go.mod h1:EJnQ2yTCDGEG0aKu+Xitp2BiXJRFeBavk1Lfgyb3wow= +github.com/randlabs/go-webserver v1.2.0 h1:pX1DQku+I2vk/3ib/TSjFW/g+Ng1noZKcH1Rrb5JNM4= +github.com/randlabs/go-webserver v1.2.0/go.mod h1:Fd29ict+bpeUIJ7q/nw20haKn5CNEe2eJktpkwBJSjA= github.com/randlabs/rundown-protection v1.0.5 h1:Bq9GMTuWvZI3915VkVLVysayQfSfHmWwuhD6feawhfU= github.com/randlabs/rundown-protection v1.0.5/go.mod h1:rccYmsidTaGDOvZSAF9eYsLzMVvh9tj043JyTicfQ5o= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -206,9 +208,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.38.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= -github.com/valyala/fasthttp v1.39.0 h1:lW8mGeM7yydOqZKmwyMTaz/PH/A+CLgtmmcjv+OORfU= -github.com/valyala/fasthttp v1.39.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/fasthttp v1.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY= +github.com/valyala/fasthttp v1.41.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -288,6 +289,7 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -343,10 +345,10 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8= -golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/handlers.go b/handlers.go index b7c20a3..7c9e973 100644 --- a/handlers.go +++ b/handlers.go @@ -1,6 +1,8 @@ package metrics import ( + "strconv" + "github.com/prometheus/client_golang/prometheus/promhttp" webserver "github.com/randlabs/go-webserver" "github.com/randlabs/go-webserver/request" @@ -8,15 +10,29 @@ import ( // ----------------------------------------------------------------------------- +const ( + strContentTypeTextPlain = "text/plain" + strContentTypeApplicationJSON = "application/json" +) + func (mws *Controller) getHealthHandler() webserver.HandlerFunc { return func(req *request.RequestContext) error { // Get current health status from callback status := mws.healthCallback() // Send output - if len(status) > 0 { - req.WriteString(status) + if isJSON(status) { + req.SetResponseHeader("Content-Type", strContentTypeApplicationJSON) + } else { + req.SetResponseHeader("Content-Type", strContentTypeTextPlain) + } + + if !req.IsHead() { + _, _ = req.WriteString(status) + } else { + req.SetResponseHeader("Content-Length", strconv.FormatUint(uint64(int64(len(status))), 10)) } + req.Success() // Done @@ -32,3 +48,31 @@ func (mws *Controller) getMetricsHandler() webserver.HandlerFunc { }, )) } + +// ----------------------------------------------------------------------------- + +func isJSON(s string) bool { + // An official (?) method but a plain text is also considered a valid JSON + // var js interface{} + // return json.Unmarshal([]byte(s), &js) == nil + + // Our quick approach + startIdx := 0 + endIdx := len(s) + + // Skip blanks at the beginning and the end + for startIdx < endIdx && isBlank(s[startIdx]) { + startIdx += 1 + } + for endIdx > startIdx && isBlank(s[endIdx-1]) { + endIdx -= 1 + } + + return startIdx < endIdx && + ((s[startIdx] == '{' && s[endIdx-1] == '}') || + (s[startIdx] == '[' && s[endIdx-1] == ']')) +} + +func isBlank(ch byte) bool { + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' +} diff --git a/metrics.go b/metrics.go index 7ce2c1e..0fedbf1 100644 --- a/metrics.go +++ b/metrics.go @@ -6,6 +6,7 @@ import ( "crypto/tls" "errors" "fmt" + "time" "github.com/prometheus/client_golang/prometheus" webserver "github.com/randlabs/go-webserver" @@ -29,6 +30,9 @@ type Options struct { // If Server is provided, use this server instead of creating a new one. Server *webserver.Server + // Server name to use when sending response headers. Defaults to 'metrics-server'. + Name string + // Address is the bind address to attach the internal web server. Address string @@ -38,6 +42,9 @@ type Options struct { // TLSConfig optionally provides a TLS configuration for use. TLSConfig *tls.Config + // A callback to call if an error is encountered. + ListenErrorHandler webserver.ListenErrorHandler + // AccessToken is an optional access token required to access the status endpoints. AccessToken string @@ -65,31 +72,47 @@ type HealthCallback func() string // ----------------------------------------------------------------------------- +const ( + defaultServerName = "metrics-server" +) + +// ----------------------------------------------------------------------------- + // CreateController initializes and creates a new controller -func CreateController(opts Options) (*Controller, error) { +func CreateController(options Options) (*Controller, error) { var err error - if opts.HealthCallback == nil { + if options.HealthCallback == nil { return nil, errors.New("invalid health callback") } // Create metrics object mws := Controller{ rundownProt: rp.Create(), - healthCallback: opts.HealthCallback, + healthCallback: options.HealthCallback, } // Create webserver - if opts.Server != nil { - mws.server = opts.Server + if options.Server != nil { + mws.server = options.Server } else { + serverName := options.Name + if len(serverName) == 0 { + serverName = defaultServerName + } + mws.usingInternalServer = true mws.server, err = webserver.Create(webserver.Options{ - Name: "metrics-server", - Address: opts.Address, - Port: opts.Port, - EnableCompression: false, - TLSConfig: opts.TLSConfig, + Name: serverName, + Address: options.Address, + Port: options.Port, + ReadTimeout: 10 * time.Second, // 10 seconds for reading and writing a metrics request + WriteTimeout: 10 * time.Second, // should be enough. + MaxRequestBodySize: 512, // Currently, no POST endpoints but leave a small buffer for future requests. + EnableCompression: false, + ListenErrorHandler: options.ListenErrorHandler, + TLSConfig: options.TLSConfig, + MinReqFileDescs: 16, }) if err != nil { mws.Destroy() @@ -106,36 +129,37 @@ func CreateController(opts Options) (*Controller, error) { // Add middlewares middlewares := make([]webserver.MiddlewareFunc, 0) - if len(opts.Middlewares) > 0 { - middlewares = append(middlewares, opts.Middlewares...) + if len(options.Middlewares) > 0 { + middlewares = append(middlewares, options.Middlewares...) } middlewares = append(middlewares, mws.createAliveMiddleware()) - if opts.DisableClientCache { + if options.DisableClientCache { middlewares = append(middlewares, middleware.DisableClientCache()) } - if opts.IncludeCORS { + if options.IncludeCORS { middlewares = append(middlewares, middleware.DefaultCORS()) } // Create middlewares with authorization middlewaresWithAuth := make([]webserver.MiddlewareFunc, len(middlewares)) copy(middlewaresWithAuth, middlewares) - if len(opts.AccessToken) > 0 { - middlewaresWithAuth = append(middlewaresWithAuth, middleware.ProtectedWithToken(opts.AccessToken)) + if len(options.AccessToken) > 0 { + middlewaresWithAuth = append(middlewaresWithAuth, middleware.ProtectedWithToken(options.AccessToken)) } // Add health handler to web server - if opts.RequestAccessTokenInHealth { - mws.server.GET("/health", mws.getHealthHandler(), middlewaresWithAuth...) - } else { - mws.server.GET("/health", mws.getHealthHandler(), middlewares...) + m := middlewares + if options.RequestAccessTokenInHealth { + m = middlewaresWithAuth } + mws.server.GET("/health", mws.getHealthHandler(), m...) + mws.server.HEAD("/health", mws.getHealthHandler(), m...) // Add metrics handler to web server mws.server.GET("/metrics", mws.getMetricsHandler(), middlewaresWithAuth...) // Add debug profiles handler to web server - if opts.EnableDebugProfiles { + if options.EnableDebugProfiles { mws.server.ServeDebugProfiles("/debug/pprof", middlewaresWithAuth...) } diff --git a/middlewares.go b/middlewares.go index 012a938..16f4764 100644 --- a/middlewares.go +++ b/middlewares.go @@ -20,7 +20,6 @@ func (mws *Controller) createAliveMiddleware() webserver.MiddlewareFunc { req.Error("", http.StatusServiceUnavailable) return nil } - } } }