Decoupled middleware
This commit is contained in:
parent
ee5f821b67
commit
b4aba437e5
6 changed files with 8 additions and 131 deletions
5
go.mod
5
go.mod
|
@ -24,14 +24,15 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/assert/v2 v2.4.1
|
github.com/alecthomas/assert/v2 v2.4.1
|
||||||
|
github.com/atridadl/bunrouter.middleware v0.2.0
|
||||||
github.com/go-redis/redismock/v9 v9.2.0
|
github.com/go-redis/redismock/v9 v9.2.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.4.0
|
github.com/redis/go-redis/v9 v9.4.0
|
||||||
github.com/resendlabs/resend-go v1.7.0
|
github.com/resendlabs/resend-go v1.7.0
|
||||||
github.com/unrolled/secure v1.14.0
|
github.com/unrolled/secure v1.14.0 // indirect
|
||||||
github.com/uptrace/bunrouter v1.0.21
|
github.com/uptrace/bunrouter v1.0.21
|
||||||
github.com/uptrace/bunrouter/extra/reqlog v1.0.21
|
github.com/uptrace/bunrouter/extra/reqlog v1.0.21
|
||||||
github.com/yuin/goldmark v1.6.0
|
github.com/yuin/goldmark v1.6.0
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -6,6 +6,8 @@ github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurG
|
||||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||||
github.com/alecthomas/repr v0.3.0 h1:NeYzUPfjjlqHY4KtzgKJiWd6sVq2eNUPTi34PiFGjY8=
|
github.com/alecthomas/repr v0.3.0 h1:NeYzUPfjjlqHY4KtzgKJiWd6sVq2eNUPTi34PiFGjY8=
|
||||||
github.com/alecthomas/repr v0.3.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.3.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
|
github.com/atridadl/bunrouter.middleware v0.2.0 h1:pEcQuYL9CJD/25Ff2ljtXfoDXgAo89iwndeeC4R0wJs=
|
||||||
|
github.com/atridadl/bunrouter.middleware v0.2.0/go.mod h1:ranjgPw1Y8Rds6sV5ZducCligVzhVukraOuVrD7EVBE=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
|
@ -69,12 +71,8 @@ github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
|
||||||
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
||||||
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
|
||||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
|
||||||
go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
|
go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
|
||||||
go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
|
go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
|
||||||
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
|
|
||||||
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
|
||||||
go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
|
go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
|
||||||
go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
|
go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
|
||||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||||
|
|
6
main.go
6
main.go
|
@ -7,9 +7,9 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"goth.stack/api"
|
"goth.stack/api"
|
||||||
"goth.stack/middleware"
|
|
||||||
"goth.stack/pages"
|
"goth.stack/pages"
|
||||||
|
|
||||||
|
bunroutermiddleware "github.com/atridadl/bunrouter.middleware"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/uptrace/bunrouter"
|
"github.com/uptrace/bunrouter"
|
||||||
"github.com/uptrace/bunrouter/extra/reqlog"
|
"github.com/uptrace/bunrouter/extra/reqlog"
|
||||||
|
@ -30,7 +30,7 @@ func main() {
|
||||||
|
|
||||||
// Initialize router
|
// Initialize router
|
||||||
router := bunrouter.New(
|
router := bunrouter.New(
|
||||||
bunrouter.Use(reqlog.NewMiddleware(), middleware.RequestID, middleware.SecureHeaders),
|
bunrouter.Use(reqlog.NewMiddleware(), bunroutermiddleware.RequestID, bunroutermiddleware.SecureHeaders),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Static server
|
// Static server
|
||||||
|
@ -42,7 +42,7 @@ func main() {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Page routes
|
// Page routes
|
||||||
pageGroup := router.NewGroup("", bunrouter.Use(middleware.NewRateLimiter(50).RateLimit))
|
pageGroup := router.NewGroup("", bunrouter.Use(bunroutermiddleware.NewRateLimiter(50).RateLimit))
|
||||||
pageGroup.GET("/", pages.Home)
|
pageGroup.GET("/", pages.Home)
|
||||||
pageGroup.GET("/blog", pages.Blog)
|
pageGroup.GET("/blog", pages.Blog)
|
||||||
pageGroup.GET("/post/:post", pages.Post)
|
pageGroup.GET("/post/:post", pages.Post)
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/uptrace/bunrouter"
|
|
||||||
)
|
|
||||||
|
|
||||||
type rateLimiter struct {
|
|
||||||
visitors map[string]*visitor
|
|
||||||
mu sync.Mutex
|
|
||||||
rps int
|
|
||||||
}
|
|
||||||
|
|
||||||
type visitor struct {
|
|
||||||
firstSeen time.Time
|
|
||||||
requests int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRateLimiter(rps int) *rateLimiter {
|
|
||||||
return &rateLimiter{
|
|
||||||
visitors: make(map[string]*visitor),
|
|
||||||
rps: rps,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rl *rateLimiter) RateLimit(next bunrouter.HandlerFunc) bunrouter.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, req bunrouter.Request) error {
|
|
||||||
rl.mu.Lock()
|
|
||||||
defer rl.mu.Unlock()
|
|
||||||
|
|
||||||
ip, _, err := net.SplitHostPort(req.RemoteAddr)
|
|
||||||
if err != nil {
|
|
||||||
// handle error, e.g., return an HTTP 500 error
|
|
||||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
v, exists := rl.visitors[ip]
|
|
||||||
if !exists || time.Since(v.firstSeen) > 1*time.Minute {
|
|
||||||
v = &visitor{
|
|
||||||
firstSeen: time.Now(),
|
|
||||||
}
|
|
||||||
rl.visitors[ip] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
v.requests++
|
|
||||||
|
|
||||||
// Limit each IP to rps requests per minute
|
|
||||||
if v.requests > rl.rps {
|
|
||||||
http.Error(w, "Too many requests", http.StatusTooManyRequests)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(w, req)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/uptrace/bunrouter"
|
|
||||||
)
|
|
||||||
|
|
||||||
type contextKey string
|
|
||||||
|
|
||||||
func (c contextKey) String() string {
|
|
||||||
return string(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
HeaderXRequestID = "X-Request-ID"
|
|
||||||
requestIDKey = contextKey("requestID")
|
|
||||||
)
|
|
||||||
|
|
||||||
func RequestID(next bunrouter.HandlerFunc) bunrouter.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, req bunrouter.Request) error {
|
|
||||||
reqID := req.Header.Get(HeaderXRequestID)
|
|
||||||
if reqID == "" {
|
|
||||||
reqID = uuid.New().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.WithValue(req.Context(), requestIDKey, reqID)
|
|
||||||
|
|
||||||
w.Header().Set(HeaderXRequestID, reqID)
|
|
||||||
return next(w, req.WithContext(ctx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetRequestID(ctx context.Context) string {
|
|
||||||
if reqID, ok := ctx.Value(requestIDKey).(string); ok {
|
|
||||||
return reqID
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/unrolled/secure"
|
|
||||||
"github.com/uptrace/bunrouter"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SecureHeaders(next bunrouter.HandlerFunc) bunrouter.HandlerFunc {
|
|
||||||
secureMiddleware := secure.New(secure.Options{
|
|
||||||
FrameDeny: true,
|
|
||||||
ContentTypeNosniff: true,
|
|
||||||
BrowserXssFilter: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
return func(w http.ResponseWriter, req bunrouter.Request) error {
|
|
||||||
secureMiddleware.HandlerFuncWithNext(w, req.Request, nil)
|
|
||||||
return next(w, req)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue