mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2024-11-26 22:30:25 +00:00
[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
10 changed files with 63 additions and 55 deletions
|
@ -183,14 +183,21 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
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
|
||||
authModule.Route(router)
|
||||
clientModule.Route(router)
|
||||
fileserverModule.Route(router)
|
||||
wellKnownModule.Route(router)
|
||||
nodeInfoModule.Route(router)
|
||||
activityPubModule.Route(router)
|
||||
webModule.Route(router)
|
||||
authModule.Route(router, clLimit, gzip)
|
||||
clientModule.Route(router, clLimit, gzip)
|
||||
fileserverModule.Route(router, fsLimit)
|
||||
wellKnownModule.Route(router, gzip, s2sLimit)
|
||||
nodeInfoModule.Route(router, s2sLimit, gzip)
|
||||
activityPubModule.Route(router, s2sLimit, gzip)
|
||||
webModule.Route(router, fsLimit, gzip)
|
||||
|
||||
gts, err := gotosocial.NewServer(dbService, router, federator, mediaManager)
|
||||
if err != nil {
|
||||
|
@ -208,8 +215,8 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
|
||||
// catch shutdown signals from the operating system
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
|
||||
sig := <-sigs
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
sig := <-sigs // block until signal received
|
||||
log.Infof("received signal %s, shutting down", sig)
|
||||
|
||||
// close down all running services in order
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"context"
|
||||
"net/url"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/emoji"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/users"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
|
@ -37,20 +38,20 @@ type ActivityPub struct {
|
|||
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
|
||||
emojiGroup := r.AttachGroup("emoji")
|
||||
usersGroup := r.AttachGroup("users")
|
||||
|
||||
// instantiate + attach shared, non-global middlewares to both of these groups
|
||||
var (
|
||||
rateLimitMiddleware = middleware.RateLimit() // nolint:contextcheck
|
||||
signatureCheckMiddleware = middleware.SignatureCheck(a.isURIBlocked)
|
||||
gzipMiddleware = middleware.Gzip()
|
||||
cacheControlMiddleware = middleware.CacheControl("no-store")
|
||||
)
|
||||
emojiGroup.Use(rateLimitMiddleware, signatureCheckMiddleware, gzipMiddleware, cacheControlMiddleware)
|
||||
usersGroup.Use(rateLimitMiddleware, signatureCheckMiddleware, gzipMiddleware, cacheControlMiddleware)
|
||||
emojiGroup.Use(m...)
|
||||
usersGroup.Use(m...)
|
||||
emojiGroup.Use(signatureCheckMiddleware, cacheControlMiddleware)
|
||||
usersGroup.Use(signatureCheckMiddleware, cacheControlMiddleware)
|
||||
|
||||
a.emoji.Route(emojiGroup.Handle)
|
||||
a.users.Route(usersGroup.Handle)
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/auth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
|
@ -36,20 +37,20 @@ type Auth struct {
|
|||
}
|
||||
|
||||
// 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
|
||||
authGroup := r.AttachGroup("auth")
|
||||
oauthGroup := r.AttachGroup("oauth")
|
||||
|
||||
// instantiate + attach shared, non-global middlewares to both of these groups
|
||||
var (
|
||||
rateLimitMiddleware = middleware.RateLimit() // nolint:contextcheck
|
||||
gzipMiddleware = middleware.Gzip()
|
||||
cacheControlMiddleware = middleware.CacheControl("private", "max-age=120")
|
||||
sessionMiddleware = middleware.Session(a.sessionName, a.routerSession.Auth, a.routerSession.Crypt)
|
||||
)
|
||||
authGroup.Use(rateLimitMiddleware, gzipMiddleware, cacheControlMiddleware, sessionMiddleware)
|
||||
oauthGroup.Use(rateLimitMiddleware, gzipMiddleware, cacheControlMiddleware, sessionMiddleware)
|
||||
authGroup.Use(m...)
|
||||
oauthGroup.Use(m...)
|
||||
authGroup.Use(cacheControlMiddleware, sessionMiddleware)
|
||||
oauthGroup.Use(cacheControlMiddleware, sessionMiddleware)
|
||||
|
||||
a.auth.RouteAuth(authGroup.Handle)
|
||||
a.auth.RouteOauth(oauthGroup.Handle)
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/admin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/apps"
|
||||
|
@ -67,15 +68,14 @@ type Client struct {
|
|||
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
|
||||
apiGroup := r.AttachGroup("api")
|
||||
|
||||
// attach non-global middlewares appropriate to the client api
|
||||
apiGroup.Use(m...)
|
||||
apiGroup.Use(
|
||||
middleware.TokenCheck(c.db, c.processor.OAuthValidateBearerToken),
|
||||
middleware.RateLimit(),
|
||||
middleware.Gzip(),
|
||||
middleware.CacheControl("no-store"), // never cache api responses
|
||||
)
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/fileserver"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
|
@ -29,12 +30,12 @@ type Fileserver struct {
|
|||
fileserver *fileserver.Module
|
||||
}
|
||||
|
||||
func (f *Fileserver) Route(r router.Router) {
|
||||
func (f *Fileserver) Route(r router.Router, m ...gin.HandlerFunc) {
|
||||
fileserverGroup := r.AttachGroup("fileserver")
|
||||
|
||||
// attach middlewares appropriate for this group
|
||||
fileserverGroup.Use(m...)
|
||||
fileserverGroup.Use(
|
||||
middleware.RateLimit(),
|
||||
// Since we'll never host different files at the same
|
||||
// URL (bc the ULIDs are generated per piece of media),
|
||||
// it's sensible and safe to use a long cache here, so
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/nodeinfo"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
|
@ -29,15 +30,15 @@ type NodeInfo struct {
|
|||
nodeInfo *nodeinfo.Module
|
||||
}
|
||||
|
||||
func (w *NodeInfo) Route(r router.Router) {
|
||||
func (w *NodeInfo) Route(r router.Router, m ...gin.HandlerFunc) {
|
||||
// group nodeinfo endpoints together
|
||||
nodeInfoGroup := r.AttachGroup("nodeinfo")
|
||||
|
||||
// attach middlewares appropriate for this group
|
||||
nodeInfoGroup.Use(m...)
|
||||
nodeInfoGroup.Use(
|
||||
middleware.Gzip(),
|
||||
middleware.RateLimit(),
|
||||
middleware.CacheControl("public", "max-age=120"), // allow cache for 2 minutes
|
||||
// allow cache for 2 minutes
|
||||
middleware.CacheControl("public", "max-age=120"),
|
||||
)
|
||||
|
||||
w.nodeInfo.Route(nodeInfoGroup.Handle)
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/nodeinfo"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/webfinger"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
||||
|
@ -31,14 +32,13 @@ type WellKnown struct {
|
|||
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
|
||||
wellKnownGroup := r.AttachGroup(".well-known")
|
||||
|
||||
// attach middlewares appropriate for this group
|
||||
wellKnownGroup.Use(m...)
|
||||
wellKnownGroup.Use(
|
||||
middleware.Gzip(),
|
||||
middleware.RateLimit(),
|
||||
// allow .well-known responses to be cached for 2 minutes
|
||||
middleware.CacheControl("public", "max-age=120"),
|
||||
)
|
||||
|
|
|
@ -19,12 +19,18 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
ginGzip "github.com/gin-contrib/gzip"
|
||||
"github.com/gin-contrib/gzip"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Gzip returns a gzip gin middleware using default compression.
|
||||
func Gzip() gin.HandlerFunc {
|
||||
// todo: make this configurable
|
||||
return ginGzip.Gzip(ginGzip.DefaultCompression)
|
||||
const enabled = true
|
||||
|
||||
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"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/ulule/limiter/v3"
|
||||
limitergin "github.com/ulule/limiter/v3/drivers/middleware/gin"
|
||||
"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,
|
||||
// which performs no rate limiting.
|
||||
func RateLimit() gin.HandlerFunc {
|
||||
// only enable rate limit middleware if configured
|
||||
// advanced-rate-limit-requests is greater than 0
|
||||
rateLimitRequests := config.GetAdvancedRateLimitRequests()
|
||||
if rateLimitRequests <= 0 {
|
||||
func RateLimit(limit int) gin.HandlerFunc {
|
||||
if limit <= 0 {
|
||||
// use noop middleware if ratelimiting is disabled
|
||||
return func(c *gin.Context) {}
|
||||
return func(ctx *gin.Context) {}
|
||||
}
|
||||
|
||||
rate := limiter.Rate{
|
||||
Period: rateLimitPeriod,
|
||||
Limit: int64(rateLimitRequests),
|
||||
}
|
||||
|
||||
limiterInstance := limiter.New(
|
||||
limiter := limiter.New(
|
||||
memory.NewStore(),
|
||||
rate,
|
||||
limiter.Rate{Period: rateLimitPeriod, Limit: int64(limit)},
|
||||
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"})
|
||||
}
|
||||
|
||||
middleware := limitergin.NewMiddleware(
|
||||
limiterInstance,
|
||||
limitergin.WithLimitReachedHandler(limitReachedHandler), // use custom rate limit reached error
|
||||
return limitergin.NewMiddleware(
|
||||
limiter,
|
||||
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
|
||||
assetsGroup := r.AttachGroup(assetsPathPrefix)
|
||||
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
|
||||
assetsGroup.Use(m.assetsCacheControlMiddleware(fs))
|
||||
assetsGroup.Use(mi...)
|
||||
|
||||
// serve static file system in the root of this group,
|
||||
// will end up being something like "/assets/"
|
||||
|
|
Loading…
Reference in a new issue