diff --git a/cmd/server/limiter.go b/cmd/server/limiter.go deleted file mode 100644 index 3f7d6ff..0000000 --- a/cmd/server/limiter.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "time" - - "github.com/ulule/limiter/v3" - "github.com/ulule/limiter/v3/drivers/middleware/stdlib" - "github.com/ulule/limiter/v3/drivers/store/memory" -) - -// newRateLimitedHandler creates a new middleware based on ulule/limiter package that -// limits the request rate that is sent to the specified handler. -// The returned rate-limited handler will allow up to rps requests per second to -// handler. When the rate exceeds the limit, a "429 Too Many Requests" response will be -// sent back without invoking the wrapped handler. -func newRateLimitedHandler(rps int64, handler http.Handler) (http.Handler, error) { - if rps <= 0 { - return nil, fmt.Errorf("rps cannot be negative (rps = %d)", rps) - } - - store := memory.NewStore() - rate := limiter.Rate{ - Period: time.Second, - Limit: rps, - } - instance := limiter.New(store, rate) - middleware := stdlib.NewMiddleware(instance) - - return middleware.Handler(handler), nil -} diff --git a/cmd/server/main.go b/cmd/server/main.go index bc7c020..a5e661b 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -12,9 +12,10 @@ import ( func main() { var dbHost, dbUser, dbPass, dbName, migrationsPath string - var port, rps, dbPort int + var port, dbPort int + var rps int64 flag.IntVar(&port, "port", 8080, "Port where the server is listening for connections.") - flag.IntVar(&rps, "rps", 100, "Rate limit expressed in requests per second (per client)") + flag.Int64Var(&rps, "rps", 100, "Rate limit expressed in requests per second (per client)") flag.StringVar(&dbHost, "dbhost", "localhost", "Address of the server that hosts the DB") flag.IntVar(&dbPort, "dbport", 5432, "Port where the DB server is listening for connections") @@ -55,10 +56,11 @@ func main() { } apiHandler, prometheusHandler := newMeasuredHandler(apiHandler) - apiHandler, err = newRateLimitedHandler(int64(rps), apiHandler) + apiHandler, err = newRateLimitedHandler(rps, apiHandler) if err != nil { log.Panicf("Error creating rate limiter middleware: %v", err) } + apiHandler = newRecoverableHandler(apiHandler) mux := http.NewServeMux() mux.Handle("/metrics", prometheusHandler) diff --git a/cmd/server/metrics.go b/cmd/server/metrics.go deleted file mode 100644 index 741a082..0000000 --- a/cmd/server/metrics.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/prometheus/client_golang/prometheus/promhttp" - metrics "github.com/slok/go-http-metrics/metrics/prometheus" - "github.com/slok/go-http-metrics/middleware" -) - -// newMeasuredHandler creates a middleware that take essential metrics about -// the handler being measured, such as number of requests, duration of each request, -// concurrent or in-flight requests and response size. -// This function returns two handlers, the handler being measured and a Prometheus -// handler that exposes the metrics being collected -func newMeasuredHandler(handler http.Handler) (measuredH http.Handler, metricsH http.Handler) { - recorder := metrics.NewRecorder(metrics.Config{ - Prefix: "pAPI", - }) - mdlw := middleware.New(middleware.Config{ - Recorder: recorder, - }) - - return mdlw.Handler("", handler), promhttp.Handler() -} diff --git a/cmd/server/middleware.go b/cmd/server/middleware.go new file mode 100644 index 0000000..2697e75 --- /dev/null +++ b/cmd/server/middleware.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "net/http" + "time" + + "github.com/prometheus/client_golang/prometheus/promhttp" + metrics "github.com/slok/go-http-metrics/metrics/prometheus" + "github.com/slok/go-http-metrics/middleware" + "github.com/ulule/limiter/v3" + "github.com/ulule/limiter/v3/drivers/middleware/stdlib" + "github.com/ulule/limiter/v3/drivers/store/memory" + "github.com/unrolled/recovery" +) + +// newMeasuredHandler creates a middleware that take essential metrics about +// the handler being measured, such as number of requests, duration of each request, +// concurrent or in-flight requests and response size. +// This function returns two handlers, the handler being measured and a Prometheus +// handler that exposes the metrics being collected +func newMeasuredHandler(handler http.Handler) (measuredH http.Handler, metricsH http.Handler) { + recorder := metrics.NewRecorder(metrics.Config{ + Prefix: "pAPI", + }) + mdlw := middleware.New(middleware.Config{ + Recorder: recorder, + }) + + return mdlw.Handler("", handler), promhttp.Handler() +} + +// newRateLimitedHandler creates a new middleware based on ulule/limiter package that +// limits the request rate that is sent to the specified handler. +// The returned rate-limited handler will allow up to rps requests per second to +// handler. When the rate exceeds the limit, a "429 Too Many Requests" response will be +// sent back without invoking the wrapped handler. +func newRateLimitedHandler(rps int64, handler http.Handler) (http.Handler, error) { + if rps <= 0 { + return nil, fmt.Errorf("rps cannot be negative (rps = %d)", rps) + } + + store := memory.NewStore() + rate := limiter.Rate{ + Period: time.Second, + Limit: rps, + } + instance := limiter.New(store, rate) + middleware := stdlib.NewMiddleware(instance) + + return middleware.Handler(handler), nil +} + +// newRecoveredHandler adds a basic panic recovery middleware so that clients +// get a 500 Internal Server Error when something goes wrong +func newRecoverableHandler(handler http.Handler) http.Handler { + rec := recovery.New() + return rec.Handler(handler) +} diff --git a/go.mod b/go.mod index 7bfa586..c97cfc8 100644 --- a/go.mod +++ b/go.mod @@ -20,4 +20,5 @@ require ( github.com/prometheus/client_golang v0.9.3 github.com/slok/go-http-metrics v0.4.0 github.com/ulule/limiter/v3 v3.2.0 + github.com/unrolled/recovery v0.0.0-20170109144926-b19e1efea904 ) diff --git a/go.sum b/go.sum index 0b3a266..ae0ce94 100644 --- a/go.sum +++ b/go.sum @@ -250,6 +250,7 @@ github.com/slok/go-http-metrics v0.4.0/go.mod h1:ZRJk+3AdSpQ0IUFseoCHaLE0Tpel+3n github.com/slok/go-prometheus-middleware v0.4.0/go.mod h1:dEbMQSF4RMuY7tG5XJVTxsHnqVs/AJqTJtpOFk7x/yo= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= @@ -259,6 +260,8 @@ github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJ github.com/ugorji/go/codec v0.0.0-20180831062425-e253f1f20942/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ulule/limiter/v3 v3.2.0 h1:LVG8PirlwDZDVFHzWEqt5K2CTBrCVUKSpIhJ4yDHE7Y= github.com/ulule/limiter/v3 v3.2.0/go.mod h1:hgLFsUPxhPqrgqqLhtdhiwfI1PXAhq//DIrbANjAX5o= +github.com/unrolled/recovery v0.0.0-20170109144926-b19e1efea904 h1:e9zUexNIJo01P0VOfb0KiiE50AWfN1YzDq3kuvuf7nk= +github.com/unrolled/recovery v0.0.0-20170109144926-b19e1efea904/go.mod h1:KpJfrwoP/I2+S0o3Ywl0+HYSSDzkNw8UBSiMDvRFeSs= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=