[chore] shuffle middleware to split rate limitting into client/s2s/fileserver, share gzip middleware globally (#1290)
Signed-off-by: kim <grufwub@gmail.com> Signed-off-by: kim <grufwub@gmail.com>
This commit is contained in:
parent
9ecb1c8aa5
commit
71dfea7e47
|
@ -183,14 +183,21 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
||||||
webModule = web.New(processor) // web pages + user profiles + settings panels etc
|
webModule = web.New(processor) // web pages + user profiles + settings panels etc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// create required middleware
|
||||||
|
limit := config.GetAdvancedRateLimitRequests()
|
||||||
|
gzip := middleware.Gzip() // all except fileserver
|
||||||
|
clLimit := middleware.RateLimit(limit) // client api
|
||||||
|
s2sLimit := middleware.RateLimit(limit) // server-to-server (AP)
|
||||||
|
fsLimit := middleware.RateLimit(limit) // fileserver / web templates
|
||||||
|
|
||||||
// these should be routed in order
|
// these should be routed in order
|
||||||
authModule.Route(router)
|
authModule.Route(router, clLimit, gzip)
|
||||||
clientModule.Route(router)
|
clientModule.Route(router, clLimit, gzip)
|
||||||
fileserverModule.Route(router)
|
fileserverModule.Route(router, fsLimit)
|
||||||
wellKnownModule.Route(router)
|
wellKnownModule.Route(router, gzip, s2sLimit)
|
||||||
nodeInfoModule.Route(router)
|
nodeInfoModule.Route(router, s2sLimit, gzip)
|
||||||
activityPubModule.Route(router)
|
activityPubModule.Route(router, s2sLimit, gzip)
|
||||||
webModule.Route(router)
|
webModule.Route(router, fsLimit, gzip)
|
||||||
|
|
||||||
gts, err := gotosocial.NewServer(dbService, router, federator, mediaManager)
|
gts, err := gotosocial.NewServer(dbService, router, federator, mediaManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -208,8 +215,8 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
||||||
|
|
||||||
// catch shutdown signals from the operating system
|
// catch shutdown signals from the operating system
|
||||||
sigs := make(chan os.Signal, 1)
|
sigs := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
sig := <-sigs
|
sig := <-sigs // block until signal received
|
||||||
log.Infof("received signal %s, shutting down", sig)
|
log.Infof("received signal %s, shutting down", sig)
|
||||||
|
|
||||||
// close down all running services in order
|
// close down all running services in order
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/emoji"
|
"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/emoji"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/users"
|
"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/users"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
@ -37,20 +38,20 @@ type ActivityPub struct {
|
||||||
isURIBlocked func(context.Context, *url.URL) (bool, db.Error)
|
isURIBlocked func(context.Context, *url.URL) (bool, db.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ActivityPub) Route(r router.Router) {
|
func (a *ActivityPub) Route(r router.Router, m ...gin.HandlerFunc) {
|
||||||
// create groupings for the 'emoji' and 'users' prefixes
|
// create groupings for the 'emoji' and 'users' prefixes
|
||||||
emojiGroup := r.AttachGroup("emoji")
|
emojiGroup := r.AttachGroup("emoji")
|
||||||
usersGroup := r.AttachGroup("users")
|
usersGroup := r.AttachGroup("users")
|
||||||
|
|
||||||
// instantiate + attach shared, non-global middlewares to both of these groups
|
// instantiate + attach shared, non-global middlewares to both of these groups
|
||||||
var (
|
var (
|
||||||
rateLimitMiddleware = middleware.RateLimit() // nolint:contextcheck
|
|
||||||
signatureCheckMiddleware = middleware.SignatureCheck(a.isURIBlocked)
|
signatureCheckMiddleware = middleware.SignatureCheck(a.isURIBlocked)
|
||||||
gzipMiddleware = middleware.Gzip()
|
|
||||||
cacheControlMiddleware = middleware.CacheControl("no-store")
|
cacheControlMiddleware = middleware.CacheControl("no-store")
|
||||||
)
|
)
|
||||||
emojiGroup.Use(rateLimitMiddleware, signatureCheckMiddleware, gzipMiddleware, cacheControlMiddleware)
|
emojiGroup.Use(m...)
|
||||||
usersGroup.Use(rateLimitMiddleware, signatureCheckMiddleware, gzipMiddleware, cacheControlMiddleware)
|
usersGroup.Use(m...)
|
||||||
|
emojiGroup.Use(signatureCheckMiddleware, cacheControlMiddleware)
|
||||||
|
usersGroup.Use(signatureCheckMiddleware, cacheControlMiddleware)
|
||||||
|
|
||||||
a.emoji.Route(emojiGroup.Handle)
|
a.emoji.Route(emojiGroup.Handle)
|
||||||
a.users.Route(usersGroup.Handle)
|
a.users.Route(usersGroup.Handle)
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/auth"
|
"github.com/superseriousbusiness/gotosocial/internal/api/auth"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
@ -36,20 +37,20 @@ type Auth struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route attaches 'auth' and 'oauth' groups to the given router.
|
// Route attaches 'auth' and 'oauth' groups to the given router.
|
||||||
func (a *Auth) Route(r router.Router) {
|
func (a *Auth) Route(r router.Router, m ...gin.HandlerFunc) {
|
||||||
// create groupings for the 'auth' and 'oauth' prefixes
|
// create groupings for the 'auth' and 'oauth' prefixes
|
||||||
authGroup := r.AttachGroup("auth")
|
authGroup := r.AttachGroup("auth")
|
||||||
oauthGroup := r.AttachGroup("oauth")
|
oauthGroup := r.AttachGroup("oauth")
|
||||||
|
|
||||||
// instantiate + attach shared, non-global middlewares to both of these groups
|
// instantiate + attach shared, non-global middlewares to both of these groups
|
||||||
var (
|
var (
|
||||||
rateLimitMiddleware = middleware.RateLimit() // nolint:contextcheck
|
|
||||||
gzipMiddleware = middleware.Gzip()
|
|
||||||
cacheControlMiddleware = middleware.CacheControl("private", "max-age=120")
|
cacheControlMiddleware = middleware.CacheControl("private", "max-age=120")
|
||||||
sessionMiddleware = middleware.Session(a.sessionName, a.routerSession.Auth, a.routerSession.Crypt)
|
sessionMiddleware = middleware.Session(a.sessionName, a.routerSession.Auth, a.routerSession.Crypt)
|
||||||
)
|
)
|
||||||
authGroup.Use(rateLimitMiddleware, gzipMiddleware, cacheControlMiddleware, sessionMiddleware)
|
authGroup.Use(m...)
|
||||||
oauthGroup.Use(rateLimitMiddleware, gzipMiddleware, cacheControlMiddleware, sessionMiddleware)
|
oauthGroup.Use(m...)
|
||||||
|
authGroup.Use(cacheControlMiddleware, sessionMiddleware)
|
||||||
|
oauthGroup.Use(cacheControlMiddleware, sessionMiddleware)
|
||||||
|
|
||||||
a.auth.RouteAuth(authGroup.Handle)
|
a.auth.RouteAuth(authGroup.Handle)
|
||||||
a.auth.RouteOauth(oauthGroup.Handle)
|
a.auth.RouteOauth(oauthGroup.Handle)
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/admin"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/admin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/apps"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/apps"
|
||||||
|
@ -67,15 +68,14 @@ type Client struct {
|
||||||
user *user.Module // api/v1/user
|
user *user.Module // api/v1/user
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Route(r router.Router) {
|
func (c *Client) Route(r router.Router, m ...gin.HandlerFunc) {
|
||||||
// create a new group on the top level client 'api' prefix
|
// create a new group on the top level client 'api' prefix
|
||||||
apiGroup := r.AttachGroup("api")
|
apiGroup := r.AttachGroup("api")
|
||||||
|
|
||||||
// attach non-global middlewares appropriate to the client api
|
// attach non-global middlewares appropriate to the client api
|
||||||
|
apiGroup.Use(m...)
|
||||||
apiGroup.Use(
|
apiGroup.Use(
|
||||||
middleware.TokenCheck(c.db, c.processor.OAuthValidateBearerToken),
|
middleware.TokenCheck(c.db, c.processor.OAuthValidateBearerToken),
|
||||||
middleware.RateLimit(),
|
|
||||||
middleware.Gzip(),
|
|
||||||
middleware.CacheControl("no-store"), // never cache api responses
|
middleware.CacheControl("no-store"), // never cache api responses
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/fileserver"
|
"github.com/superseriousbusiness/gotosocial/internal/api/fileserver"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||||
|
@ -29,12 +30,12 @@ type Fileserver struct {
|
||||||
fileserver *fileserver.Module
|
fileserver *fileserver.Module
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fileserver) Route(r router.Router) {
|
func (f *Fileserver) Route(r router.Router, m ...gin.HandlerFunc) {
|
||||||
fileserverGroup := r.AttachGroup("fileserver")
|
fileserverGroup := r.AttachGroup("fileserver")
|
||||||
|
|
||||||
// attach middlewares appropriate for this group
|
// attach middlewares appropriate for this group
|
||||||
|
fileserverGroup.Use(m...)
|
||||||
fileserverGroup.Use(
|
fileserverGroup.Use(
|
||||||
middleware.RateLimit(),
|
|
||||||
// Since we'll never host different files at the same
|
// Since we'll never host different files at the same
|
||||||
// URL (bc the ULIDs are generated per piece of media),
|
// URL (bc the ULIDs are generated per piece of media),
|
||||||
// it's sensible and safe to use a long cache here, so
|
// it's sensible and safe to use a long cache here, so
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/nodeinfo"
|
"github.com/superseriousbusiness/gotosocial/internal/api/nodeinfo"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||||
|
@ -29,15 +30,15 @@ type NodeInfo struct {
|
||||||
nodeInfo *nodeinfo.Module
|
nodeInfo *nodeinfo.Module
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *NodeInfo) Route(r router.Router) {
|
func (w *NodeInfo) Route(r router.Router, m ...gin.HandlerFunc) {
|
||||||
// group nodeinfo endpoints together
|
// group nodeinfo endpoints together
|
||||||
nodeInfoGroup := r.AttachGroup("nodeinfo")
|
nodeInfoGroup := r.AttachGroup("nodeinfo")
|
||||||
|
|
||||||
// attach middlewares appropriate for this group
|
// attach middlewares appropriate for this group
|
||||||
|
nodeInfoGroup.Use(m...)
|
||||||
nodeInfoGroup.Use(
|
nodeInfoGroup.Use(
|
||||||
middleware.Gzip(),
|
// allow cache for 2 minutes
|
||||||
middleware.RateLimit(),
|
middleware.CacheControl("public", "max-age=120"),
|
||||||
middleware.CacheControl("public", "max-age=120"), // allow cache for 2 minutes
|
|
||||||
)
|
)
|
||||||
|
|
||||||
w.nodeInfo.Route(nodeInfoGroup.Handle)
|
w.nodeInfo.Route(nodeInfoGroup.Handle)
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/nodeinfo"
|
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/nodeinfo"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/webfinger"
|
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/webfinger"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
||||||
|
@ -31,14 +32,13 @@ type WellKnown struct {
|
||||||
webfinger *webfinger.Module
|
webfinger *webfinger.Module
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WellKnown) Route(r router.Router) {
|
func (w *WellKnown) Route(r router.Router, m ...gin.HandlerFunc) {
|
||||||
// group .well-known endpoints together
|
// group .well-known endpoints together
|
||||||
wellKnownGroup := r.AttachGroup(".well-known")
|
wellKnownGroup := r.AttachGroup(".well-known")
|
||||||
|
|
||||||
// attach middlewares appropriate for this group
|
// attach middlewares appropriate for this group
|
||||||
|
wellKnownGroup.Use(m...)
|
||||||
wellKnownGroup.Use(
|
wellKnownGroup.Use(
|
||||||
middleware.Gzip(),
|
|
||||||
middleware.RateLimit(),
|
|
||||||
// allow .well-known responses to be cached for 2 minutes
|
// allow .well-known responses to be cached for 2 minutes
|
||||||
middleware.CacheControl("public", "max-age=120"),
|
middleware.CacheControl("public", "max-age=120"),
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,12 +19,18 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
ginGzip "github.com/gin-contrib/gzip"
|
"github.com/gin-contrib/gzip"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Gzip returns a gzip gin middleware using default compression.
|
// Gzip returns a gzip gin middleware using default compression.
|
||||||
func Gzip() gin.HandlerFunc {
|
func Gzip() gin.HandlerFunc {
|
||||||
// todo: make this configurable
|
const enabled = true
|
||||||
return ginGzip.Gzip(ginGzip.DefaultCompression)
|
|
||||||
|
if !enabled {
|
||||||
|
// use noop middleware if gzip is disabled
|
||||||
|
return func(ctx *gin.Context) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gzip.Gzip(gzip.DefaultCompression)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
|
||||||
"github.com/ulule/limiter/v3"
|
"github.com/ulule/limiter/v3"
|
||||||
limitergin "github.com/ulule/limiter/v3/drivers/middleware/gin"
|
limitergin "github.com/ulule/limiter/v3/drivers/middleware/gin"
|
||||||
"github.com/ulule/limiter/v3/drivers/store/memory"
|
"github.com/ulule/limiter/v3/drivers/store/memory"
|
||||||
|
@ -44,34 +43,25 @@ const rateLimitPeriod = 5 * time.Minute
|
||||||
//
|
//
|
||||||
// If the config AdvancedRateLimitRequests value is <= 0, then a noop handler will be returned,
|
// If the config AdvancedRateLimitRequests value is <= 0, then a noop handler will be returned,
|
||||||
// which performs no rate limiting.
|
// which performs no rate limiting.
|
||||||
func RateLimit() gin.HandlerFunc {
|
func RateLimit(limit int) gin.HandlerFunc {
|
||||||
// only enable rate limit middleware if configured
|
if limit <= 0 {
|
||||||
// advanced-rate-limit-requests is greater than 0
|
|
||||||
rateLimitRequests := config.GetAdvancedRateLimitRequests()
|
|
||||||
if rateLimitRequests <= 0 {
|
|
||||||
// use noop middleware if ratelimiting is disabled
|
// use noop middleware if ratelimiting is disabled
|
||||||
return func(c *gin.Context) {}
|
return func(ctx *gin.Context) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
rate := limiter.Rate{
|
limiter := limiter.New(
|
||||||
Period: rateLimitPeriod,
|
|
||||||
Limit: int64(rateLimitRequests),
|
|
||||||
}
|
|
||||||
|
|
||||||
limiterInstance := limiter.New(
|
|
||||||
memory.NewStore(),
|
memory.NewStore(),
|
||||||
rate,
|
limiter.Rate{Period: rateLimitPeriod, Limit: int64(limit)},
|
||||||
limiter.WithIPv6Mask(net.CIDRMask(64, 128)), // apply /64 mask to IPv6 addresses
|
limiter.WithIPv6Mask(net.CIDRMask(64, 128)), // apply /64 mask to IPv6 addresses
|
||||||
)
|
)
|
||||||
|
|
||||||
limitReachedHandler := func(c *gin.Context) {
|
// use custom rate limit reached error
|
||||||
|
handler := func(c *gin.Context) {
|
||||||
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "rate limit reached"})
|
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "rate limit reached"})
|
||||||
}
|
}
|
||||||
|
|
||||||
middleware := limitergin.NewMiddleware(
|
return limitergin.NewMiddleware(
|
||||||
limiterInstance,
|
limiter,
|
||||||
limitergin.WithLimitReachedHandler(limitReachedHandler), // use custom rate limit reached error
|
limitergin.WithLimitReachedHandler(handler),
|
||||||
)
|
)
|
||||||
|
|
||||||
return middleware
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ func New(processor processing.Processor) *Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Module) Route(r router.Router) {
|
func (m *Module) Route(r router.Router, mi ...gin.HandlerFunc) {
|
||||||
// serve static files from assets dir at /assets
|
// serve static files from assets dir at /assets
|
||||||
assetsGroup := r.AttachGroup(assetsPathPrefix)
|
assetsGroup := r.AttachGroup(assetsPathPrefix)
|
||||||
webAssetsAbsFilePath, err := filepath.Abs(config.GetWebAssetBaseDir())
|
webAssetsAbsFilePath, err := filepath.Abs(config.GetWebAssetBaseDir())
|
||||||
|
@ -80,6 +80,7 @@ func (m *Module) Route(r router.Router) {
|
||||||
|
|
||||||
// use the cache middleware on all handlers in this group
|
// use the cache middleware on all handlers in this group
|
||||||
assetsGroup.Use(m.assetsCacheControlMiddleware(fs))
|
assetsGroup.Use(m.assetsCacheControlMiddleware(fs))
|
||||||
|
assetsGroup.Use(mi...)
|
||||||
|
|
||||||
// serve static file system in the root of this group,
|
// serve static file system in the root of this group,
|
||||||
// will end up being something like "/assets/"
|
// will end up being something like "/assets/"
|
||||||
|
|
Loading…
Reference in New Issue